Compare commits
	
		
			No commits in common. "master" and "v1.0.0" have entirely different histories.
		
	
	
		
	
		
| @ -1,33 +0,0 @@ | |||||||
| # clang-format is fairly limited, and these rules are approximate: |  | ||||||
| #  - array initializers can get terribly mangled with clang-format 12.0, |  | ||||||
| #  - sometimes it still aligns with space characters, |  | ||||||
| #  - EV_DEFAULT_ and EV_A_ are always taken as identifiers, |  | ||||||
| #  - struct name NL { NL ... NL } NL name; is unachievable. |  | ||||||
| BasedOnStyle: GNU |  | ||||||
| ColumnLimit: 80 |  | ||||||
| IndentWidth: 4 |  | ||||||
| TabWidth: 4 |  | ||||||
| UseTab: ForContinuationAndIndentation |  | ||||||
| BreakBeforeBraces: Allman |  | ||||||
| SpaceAfterCStyleCast: true |  | ||||||
| AlignAfterOpenBracket: DontAlign |  | ||||||
| AlignOperands: DontAlign |  | ||||||
| AlignConsecutiveMacros: Consecutive |  | ||||||
| AllowAllArgumentsOnNextLine: false |  | ||||||
| AllowAllParametersOfDeclarationOnNextLine: false |  | ||||||
| IndentGotoLabels: false |  | ||||||
| 
 |  | ||||||
| # IncludeCategories has some potential, but it may also break the build. |  | ||||||
| # Note that the documentation says the value should be "Never". |  | ||||||
| SortIncludes: false |  | ||||||
| 
 |  | ||||||
| # This is a compromise, it generally works out aesthetically better. |  | ||||||
| BinPackArguments: false |  | ||||||
| 
 |  | ||||||
| # Unfortunately, this can't be told to align to column 40 or so. |  | ||||||
| SpacesBeforeTrailingComments: 2 |  | ||||||
| 
 |  | ||||||
| # liberty-specific macro body wrappers. |  | ||||||
| MacroBlockBegin: "BLOCK_START" |  | ||||||
| MacroBlockEnd: "BLOCK_END" |  | ||||||
| ForEachMacros: ["LIST_FOR_EACH"] |  | ||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -7,5 +7,3 @@ | |||||||
| /json-rpc-shell.files | /json-rpc-shell.files | ||||||
| /json-rpc-shell.creator* | /json-rpc-shell.creator* | ||||||
| /json-rpc-shell.includes | /json-rpc-shell.includes | ||||||
| /json-rpc-shell.cflags |  | ||||||
| /json-rpc-shell.cxxflags |  | ||||||
|  | |||||||
							
								
								
									
										131
									
								
								CMakeLists.txt
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								CMakeLists.txt
									
									
									
									
									
								
							| @ -1,5 +1,5 @@ | |||||||
| cmake_minimum_required (VERSION 3.0...3.27) | project (json-rpc-shell C) | ||||||
| project (json-rpc-shell VERSION 1.2.0 LANGUAGES C) | cmake_minimum_required (VERSION 2.8.5) | ||||||
| 
 | 
 | ||||||
| # Options | # Options | ||||||
| option (WANT_READLINE "Use GNU Readline for the UI (better)" ON) | option (WANT_READLINE "Use GNU Readline for the UI (better)" ON) | ||||||
| @ -10,73 +10,54 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC) | |||||||
| 	# -Wunused-function is pretty annoying here, as everything is static | 	# -Wunused-function is pretty annoying here, as everything is static | ||||||
| 	set (CMAKE_C_FLAGS | 	set (CMAKE_C_FLAGS | ||||||
| 		"${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Wno-unused-function") | 		"${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Wno-unused-function") | ||||||
| endif () | endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC) | ||||||
|  | 
 | ||||||
|  | # Version | ||||||
|  | set (project_VERSION_MAJOR "1") | ||||||
|  | set (project_VERSION_MINOR "0") | ||||||
|  | set (project_VERSION_PATCH "0") | ||||||
|  | 
 | ||||||
|  | set (project_VERSION "${project_VERSION_MAJOR}") | ||||||
|  | set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}") | ||||||
|  | set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}") | ||||||
| 
 | 
 | ||||||
| # For custom modules | # For custom modules | ||||||
| set (CMAKE_MODULE_PATH | set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) | ||||||
| 	"${PROJECT_SOURCE_DIR}/cmake;${PROJECT_SOURCE_DIR}/liberty/cmake") |  | ||||||
| 
 | 
 | ||||||
| # Dependencies | # Dependencies | ||||||
| find_package (Curses) | find_package (Curses) | ||||||
| find_package (Ncursesw) |  | ||||||
| find_package (PkgConfig REQUIRED) | find_package (PkgConfig REQUIRED) | ||||||
|  | pkg_check_modules (dependencies REQUIRED libcurl jansson) | ||||||
| # Note that cURL can link to a different version of libssl than we do, | # Note that cURL can link to a different version of libssl than we do, | ||||||
| # in which case the results are undefined | # in which case the results are undefined | ||||||
| pkg_check_modules (dependencies REQUIRED libcurl jansson libssl libcrypto) | pkg_check_modules (libssl REQUIRED libssl libcrypto) | ||||||
| find_package (LibEV REQUIRED) | find_package (LibEV REQUIRED) | ||||||
|  | pkg_check_modules (ncursesw ncursesw) | ||||||
| 
 | 
 | ||||||
| set (project_libraries ${dependencies_LIBRARIES} ${LibEV_LIBRARIES}) | set (project_libraries ${dependencies_LIBRARIES} | ||||||
| include_directories (${dependencies_INCLUDE_DIRS} ${LibEV_INCLUDE_DIRS}) | 	${libssl_LIBRARIES} ${LIBEV_LIBRARIES}) | ||||||
| link_directories (${dependencies_LIBRARY_DIRS}) | include_directories (${dependencies_INCLUDE_DIRS} | ||||||
|  | 	${libssl_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS}) | ||||||
| 
 | 
 | ||||||
| if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD") | if (ncursesw_FOUND) | ||||||
| 	# Need this for SIGWINCH in FreeBSD and OpenBSD respectively; | 	list (APPEND project_libraries ${ncursesw_LIBRARIES}) | ||||||
| 	# our POSIX version macros make it undefined | 	include_directories (${ncursesw_INCLUDE_DIRS}) | ||||||
| 	add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1) |  | ||||||
| elseif (APPLE) |  | ||||||
| 	add_definitions (-D_DARWIN_C_SOURCE) |  | ||||||
| endif () |  | ||||||
| 
 |  | ||||||
| # -liconv may or may not be a part of libc |  | ||||||
| find_library (iconv_LIBRARIES iconv) |  | ||||||
| if (iconv_LIBRARIES) |  | ||||||
| 	list (APPEND project_libraries ${iconv_LIBRARIES}) |  | ||||||
| endif () |  | ||||||
| 
 |  | ||||||
| include (CheckCSourceRuns) |  | ||||||
| set (CMAKE_REQUIRED_LIBRARIES ${project_libraries}) |  | ||||||
| get_property (CMAKE_REQUIRED_INCLUDES |  | ||||||
| 	DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY INCLUDE_DIRECTORIES) |  | ||||||
| CHECK_C_SOURCE_RUNS ("#include <iconv.h> |  | ||||||
| 	int main () { return iconv_open (\"UTF-8//TRANSLIT\", \"ISO-8859-1\") |  | ||||||
| 		== (iconv_t) -1; }" ICONV_ACCEPTS_TRANSLIT) |  | ||||||
| 
 |  | ||||||
| if (Ncursesw_FOUND) |  | ||||||
| 	list (APPEND project_libraries ${Ncursesw_LIBRARIES}) |  | ||||||
| 	include_directories (${Ncursesw_INCLUDE_DIRS}) |  | ||||||
| 	link_directories (${Ncursesw_LIBRARY_DIRS}) |  | ||||||
| elseif (CURSES_FOUND) | elseif (CURSES_FOUND) | ||||||
| 	list (APPEND project_libraries ${CURSES_LIBRARY}) | 	list (APPEND project_libraries ${CURSES_LIBRARY}) | ||||||
| 	include_directories (${CURSES_INCLUDE_DIR}) | 	include_directories (${CURSES_INCLUDE_DIR}) | ||||||
| else () | else (CURSES_FOUND) | ||||||
| 	message (SEND_ERROR "Curses not found") | 	message (SEND_ERROR "Curses not found") | ||||||
| endif () | endif (ncursesw_FOUND) | ||||||
| 
 | 
 | ||||||
| if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT)) | if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT)) | ||||||
| 	message (SEND_ERROR "You have to choose either GNU Readline or libedit") | 	message (SEND_ERROR "You have to choose either GNU Readline or libedit") | ||||||
| elseif (WANT_READLINE) | elseif (WANT_READLINE) | ||||||
| 	# OpenBSD's default readline is too old | 	list (APPEND project_libraries readline) | ||||||
| 	if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD") |  | ||||||
| 		include_directories (${OPENBSD_LOCALBASE}/include/ereadline) |  | ||||||
| 		list (APPEND project_libraries ereadline) |  | ||||||
| 	else () |  | ||||||
| 		list (APPEND project_libraries readline) |  | ||||||
| 	endif () |  | ||||||
| elseif (WANT_LIBEDIT) | elseif (WANT_LIBEDIT) | ||||||
| 	pkg_check_modules (libedit REQUIRED libedit) | 	pkg_check_modules (libedit REQUIRED libedit) | ||||||
| 	list (APPEND project_libraries ${libedit_LIBRARIES}) | 	list (APPEND project_libraries ${libedit_LIBRARIES}) | ||||||
| 	include_directories (${libedit_INCLUDE_DIRS}) | 	include_directories (${libedit_INCLUDE_DIRS}) | ||||||
| endif () | endif ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT)) | ||||||
| 
 | 
 | ||||||
| # Generate a configuration file | # Generate a configuration file | ||||||
| set (HAVE_READLINE "${WANT_READLINE}") | set (HAVE_READLINE "${WANT_READLINE}") | ||||||
| @ -105,43 +86,23 @@ install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) | |||||||
| install (PROGRAMS json-format.pl DESTINATION ${CMAKE_INSTALL_BINDIR}) | install (PROGRAMS json-format.pl DESTINATION ${CMAKE_INSTALL_BINDIR}) | ||||||
| install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) | install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) | ||||||
| 
 | 
 | ||||||
| # Generate documentation from text markup | # Generate documentation from program help | ||||||
| find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor) | find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor) | ||||||
| find_program (A2X_EXECUTABLE a2x) | if (NOT ASCIIDOCTOR_EXECUTABLE) | ||||||
| if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE) | 	message (FATAL_ERROR "asciidoctor not found") | ||||||
| 	message (WARNING "Neither asciidoctor nor a2x were found, " | endif (NOT ASCIIDOCTOR_EXECUTABLE) | ||||||
| 		"falling back to a substandard manual page generator") |  | ||||||
| endif () |  | ||||||
| 
 | 
 | ||||||
| foreach (page ${PROJECT_NAME}) | foreach (page ${PROJECT_NAME}) | ||||||
| 	set (page_output "${PROJECT_BINARY_DIR}/${page}.1") | 	set (page_output "${PROJECT_BINARY_DIR}/${page}.1") | ||||||
| 	list (APPEND project_MAN_PAGES "${page_output}") | 	list (APPEND project_MAN_PAGES "${page_output}") | ||||||
| 	if (ASCIIDOCTOR_EXECUTABLE) | 	add_custom_command (OUTPUT ${page_output} | ||||||
| 		add_custom_command (OUTPUT ${page_output} | 		COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage | ||||||
| 			COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage | 			-a release-version=${project_VERSION} | ||||||
| 				-a release-version=${PROJECT_VERSION} | 			"${PROJECT_SOURCE_DIR}/${page}.adoc" | ||||||
| 				-o "${page_output}" | 			-o "${page_output}" | ||||||
| 				"${PROJECT_SOURCE_DIR}/${page}.adoc" | 		DEPENDS ${page}.adoc | ||||||
| 			DEPENDS ${page}.adoc | 		COMMENT "Generating man page for ${page}" VERBATIM) | ||||||
| 			COMMENT "Generating man page for ${page}" VERBATIM) | endforeach (page) | ||||||
| 	elseif (A2X_EXECUTABLE) |  | ||||||
| 		add_custom_command (OUTPUT ${page_output} |  | ||||||
| 			COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage |  | ||||||
| 				-a release-version=${PROJECT_VERSION} |  | ||||||
| 				-D "${PROJECT_BINARY_DIR}" |  | ||||||
| 				"${PROJECT_SOURCE_DIR}/${page}.adoc" |  | ||||||
| 			DEPENDS ${page}.adoc |  | ||||||
| 			COMMENT "Generating man page for ${page}" VERBATIM) |  | ||||||
| 	else () |  | ||||||
| 		set (ASCIIMAN ${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk) |  | ||||||
| 		add_custom_command (OUTPUT ${page_output} |  | ||||||
| 			COMMAND env LC_ALL=C asciidoc-release-version=${PROJECT_VERSION} |  | ||||||
| 				awk -f ${ASCIIMAN} "${PROJECT_SOURCE_DIR}/${page}.adoc" |  | ||||||
| 				> ${page_output} |  | ||||||
| 			DEPENDS ${page}.adoc ${ASCIIMAN} |  | ||||||
| 			COMMENT "Generating man page for ${page}" VERBATIM) |  | ||||||
| 	endif () |  | ||||||
| endforeach () |  | ||||||
| 
 | 
 | ||||||
| add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES}) | add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES}) | ||||||
| 
 | 
 | ||||||
| @ -149,19 +110,23 @@ foreach (page ${project_MAN_PAGES}) | |||||||
| 	string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}") | 	string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}") | ||||||
| 	install (FILES "${page}" | 	install (FILES "${page}" | ||||||
| 		DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}") | 		DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}") | ||||||
| endforeach () | endforeach (page) | ||||||
| 
 | 
 | ||||||
| # CPack | # CPack | ||||||
| set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "A shell for JSON-RPC 2.0") | set (CPACK_PACKAGE_DESCRIPTION_SUMMARY | ||||||
|  | 	"A shell for running JSON-RPC 2.0 queries") | ||||||
| set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch") | set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch") | ||||||
| set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>") | set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>") | ||||||
| set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") | set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") | ||||||
|  | set (CPACK_PACKAGE_VERSION_MAJOR ${project_VERSION_MAJOR}) | ||||||
|  | set (CPACK_PACKAGE_VERSION_MINOR ${project_VERSION_MINOR}) | ||||||
|  | set (CPACK_PACKAGE_VERSION_PATCH ${project_VERSION_PATCH}) | ||||||
| set (CPACK_GENERATOR "TGZ;ZIP") | set (CPACK_GENERATOR "TGZ;ZIP") | ||||||
| set (CPACK_PACKAGE_FILE_NAME | set (CPACK_PACKAGE_FILE_NAME | ||||||
| 	"${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") | 	"${PROJECT_NAME}-${project_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") | ||||||
| set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${PROJECT_VERSION}") | set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${project_VERSION}") | ||||||
| set (CPACK_SOURCE_GENERATOR "TGZ;ZIP") | set (CPACK_SOURCE_GENERATOR "TGZ;ZIP") | ||||||
| set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user") | set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user") | ||||||
| set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}") | set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_VERSION}") | ||||||
| 
 | 
 | ||||||
| include (CPack) | include (CPack) | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| Copyright (c) 2014 - 2022, Přemysl Eric Janouch <p@janouch.name> | Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name> | ||||||
| 
 | 
 | ||||||
| Permission to use, copy, modify, and/or distribute this software for any | Permission to use, copy, modify, and/or distribute this software for any | ||||||
| purpose with or without fee is hereby granted. | purpose with or without fee is hereby granted. | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								NEWS
									
									
									
									
									
								
							| @ -1,33 +1,3 @@ | |||||||
| 1.2.0 (2024-12-24) |  | ||||||
| 
 |  | ||||||
|  * Add a backend for co-processes, such as language servers |  | ||||||
| 
 |  | ||||||
|  * Support reading OpenRPC documents from a file |  | ||||||
| 
 |  | ||||||
|  * Respect the NO_COLOR environment variable |  | ||||||
| 
 |  | ||||||
|  * Miscellaneous libedit (editline) fixes |  | ||||||
| 
 |  | ||||||
|  * Miscellaneous portability improvements |  | ||||||
| 
 |  | ||||||
|  * json-rpc-test-server: implement OpenRPC discovery |  | ||||||
| 
 |  | ||||||
|  * json-rpc-test-server: only serve regular files |  | ||||||
| 
 |  | ||||||
|  * json-rpc-test-server: miscellaneous WebSocket fixes |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 1.1.0 (2020-10-13) |  | ||||||
| 
 |  | ||||||
|  * Add method name tab completion using OpenRPC information |  | ||||||
| 
 |  | ||||||
|  * Bind M-Enter to insert a newline into the command line |  | ||||||
| 
 |  | ||||||
|  * json-rpc-test-server: fix a memory leak and request URI parsing |  | ||||||
| 
 |  | ||||||
|  * Miscellaneous bug fixes |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 1.0.0 (2020-09-05) | 1.0.0 (2020-09-05) | ||||||
| 
 | 
 | ||||||
|  * Initial release |  * Initial release | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								README.adoc
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.adoc
									
									
									
									
									
								
							| @ -2,25 +2,22 @@ json-rpc-shell | |||||||
| ============== | ============== | ||||||
| :compact-option: | :compact-option: | ||||||
| 
 | 
 | ||||||
| 'json-rpc-shell' is a shell for running JSON-RPC 2.0 queries. | 'json-rpc-shell' is a simple shell for running JSON-RPC 2.0 queries. | ||||||
| 
 | 
 | ||||||
| This software was originally created as a replacement for | This software has been created as a replacement for the following shell, which | ||||||
| http://software.dzhuvinov.com/json-rpc-2.0-shell.html[a different shell] made by | is written in Java: http://software.dzhuvinov.com/json-rpc-2.0-shell.html | ||||||
| Vladimir Dzhuvinov, in order to avoid Java, but has evolved since. |  | ||||||
| 
 | 
 | ||||||
| Features | Features | ||||||
| -------- | -------- | ||||||
| In addition to most of the features provided by its predecessor, you will get | In addition to most of the features provided by Vladimir Dzhuvinov's shell | ||||||
| the following niceties: | you get the following niceties: | ||||||
| 
 | 
 | ||||||
|  - configurable JSON syntax highlight, which with prettyprinting turned on |  - configurable JSON syntax highlight, which with prettyprinting turned on | ||||||
|    helps you make sense of the results significantly |    helps you make sense of the results significantly | ||||||
|  - ability to pipe output through a shell command, so that you can view the |  - ability to pipe output through a shell command, so that you can view the | ||||||
|    results in your favourite editor or redirect them to a file |    results in your favourite editor or redirect them to a file | ||||||
|  - ability to edit the input line in your favourite editor as well with Alt+E |  - ability to edit the input line in your favourite editor as well with Alt+E | ||||||
|  - WebSocket (RFC 6455) can also be used as a transport rather than HTTP |  - WebSockets (RFC 6455) can also be used as a transport rather than HTTP | ||||||
|  - even Language Server Protocol servers may be launched as a slave command |  | ||||||
|  - support for method name tab completion using OpenRPC discovery or file input |  | ||||||
| 
 | 
 | ||||||
| Documentation | Documentation | ||||||
| ------------- | ------------- | ||||||
| @ -29,17 +26,19 @@ The rest of this README will concern itself with externalities. | |||||||
| 
 | 
 | ||||||
| Packages | Packages | ||||||
| -------- | -------- | ||||||
| Regular releases are sporadic.  git master should be stable enough. | Regular releases are sporadic.  git master should be stable enough.  You can get | ||||||
| You can get a package with the latest development version using Arch Linux's | a package with the latest development version from Archlinux's AUR. | ||||||
| https://aur.archlinux.org/packages/json-rpc-shell-git[AUR], |  | ||||||
| or as a https://git.janouch.name/p/nixexprs[Nix derivation]. |  | ||||||
| 
 | 
 | ||||||
| Building | Building | ||||||
| -------- | -------- | ||||||
| Build dependencies: CMake, pkg-config, liberty (included), | Build dependencies: CMake, pkg-config, asciidoctor, | ||||||
|  http-parser (included), asciidoctor or asciidoc (recommended but optional) + |                     liberty (included), http-parser (included) + | ||||||
| Runtime dependencies: | Runtime dependencies: libev, Jansson, cURL, openssl, | ||||||
|  libev, Jansson, cURL, openssl, readline or libedit >= 2013-07-12 |                       readline or libedit >= 2013-07-12, | ||||||
|  | 
 | ||||||
|  | Avoid libedit if you can, in general it works but at the moment history is | ||||||
|  | acting up and I have no clue about fixing it.  Multiline editing is also | ||||||
|  | misbehaving there. | ||||||
| 
 | 
 | ||||||
|  $ git clone --recursive https://git.janouch.name/p/json-rpc-shell.git |  $ git clone --recursive https://git.janouch.name/p/json-rpc-shell.git | ||||||
|  $ mkdir json-rpc-shell/build |  $ mkdir json-rpc-shell/build | ||||||
| @ -56,12 +55,15 @@ Or you can try telling CMake to make a package for you.  For Debian it is: | |||||||
|  $ cpack -G DEB |  $ cpack -G DEB | ||||||
|  # dpkg -i json-rpc-shell-*.deb |  # dpkg -i json-rpc-shell-*.deb | ||||||
| 
 | 
 | ||||||
|  | Note that for versions of CMake before 2.8.9, you need to prefix `cpack` with | ||||||
|  | `fakeroot` or file ownership will end up wrong. | ||||||
|  | 
 | ||||||
| Test server | Test server | ||||||
| ----------- | ----------- | ||||||
| If you install development packages for libmagic, an included test server will | If you install development packages for libmagic, an included test server will | ||||||
| be built but not installed which provides a trivial JSON-RPC 2.0 service with | be built but not installed which provides a trivial JSON-RPC 2.0 service with | ||||||
| FastCGI, SCGI, WebSocket and LSP-like co-process interfaces.  It responds to | FastCGI, SCGI, and WebSocket interfaces.  It responds to `ping` and `date` | ||||||
| `ping` and `date`, supports OpenRPC discovery and it can serve static files. | methods and it can serve static files. | ||||||
| 
 | 
 | ||||||
| Contributing and Support | Contributing and Support | ||||||
| ------------------------ | ------------------------ | ||||||
|  | |||||||
| @ -5,16 +5,14 @@ | |||||||
| 
 | 
 | ||||||
| # Some distributions do add it, though | # Some distributions do add it, though | ||||||
| find_package (PkgConfig REQUIRED) | find_package (PkgConfig REQUIRED) | ||||||
| pkg_check_modules (LibEV QUIET libev) | pkg_check_modules (LIBEV QUIET libev) | ||||||
| 
 | 
 | ||||||
| set (required_vars LibEV_LIBRARIES) | if (NOT LIBEV_FOUND) | ||||||
| if (NOT LibEV_FOUND) | 	find_path (LIBEV_INCLUDE_DIRS ev.h) | ||||||
| 	find_path (LibEV_INCLUDE_DIRS ev.h) | 	find_library (LIBEV_LIBRARIES NAMES ev) | ||||||
| 	find_library (LibEV_LIBRARIES NAMES ev) |  | ||||||
| 	list (APPEND required_vars LibEV_INCLUDE_DIRS) |  | ||||||
| endif () |  | ||||||
| 
 | 
 | ||||||
| include (FindPackageHandleStandardArgs) | 	if (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES) | ||||||
| FIND_PACKAGE_HANDLE_STANDARD_ARGS (LibEV DEFAULT_MSG ${required_vars}) | 		set (LIBEV_FOUND TRUE) | ||||||
|  | 	endif (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES) | ||||||
|  | endif (NOT LIBEV_FOUND) | ||||||
| 
 | 
 | ||||||
| mark_as_advanced (LibEV_LIBRARIES LibEV_INCLUDE_DIRS) |  | ||||||
|  | |||||||
| @ -2,12 +2,10 @@ | |||||||
| #define CONFIG_H | #define CONFIG_H | ||||||
| 
 | 
 | ||||||
| #define PROGRAM_NAME "${PROJECT_NAME}" | #define PROGRAM_NAME "${PROJECT_NAME}" | ||||||
| #define PROGRAM_VERSION "${PROJECT_VERSION}" | #define PROGRAM_VERSION "${project_VERSION}" | ||||||
| 
 | 
 | ||||||
| #cmakedefine HAVE_READLINE | #cmakedefine HAVE_READLINE | ||||||
| #cmakedefine HAVE_EDITLINE | #cmakedefine HAVE_EDITLINE | ||||||
| 
 | 
 | ||||||
| #cmakedefine01 ICONV_ACCEPTS_TRANSLIT |  | ||||||
| 
 |  | ||||||
| #endif  // ! CONFIG_H
 | #endif  // ! CONFIG_H
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| Subproject commit ec8b5ee63f0e51191ea43bb0c6eac7bfbff3141d | Subproject commit 5d414fcb4b2ccc1ce9d6063292f9c63c9ec67b04 | ||||||
| @ -6,17 +6,16 @@ json-rpc-shell(1) | |||||||
| 
 | 
 | ||||||
| Name | Name | ||||||
| ---- | ---- | ||||||
| json-rpc-shell - a shell for JSON-RPC 2.0 | json-rpc-shell - a simple JSON-RPC 2.0 shell | ||||||
| 
 | 
 | ||||||
| Synopsis | Synopsis | ||||||
| -------- | -------- | ||||||
| *json-rpc-shell* [_OPTION_]... { _ENDPOINT_ | _COMMAND_ [_ARG_]... } | *json-rpc-shell* [_OPTION_]... _ENDPOINT_ | ||||||
| 
 | 
 | ||||||
| Description | Description | ||||||
| ----------- | ----------- | ||||||
| :colon: : |  | ||||||
| The _ENDPOINT_ must be either an HTTP or a WebSocket URL, with or without TLS | The _ENDPOINT_ must be either an HTTP or a WebSocket URL, with or without TLS | ||||||
| (i.e. one of the _http{colon}//_, _https{colon}//_, _ws://_, _wss://_ schemas). | (i.e. one of the _+++http+++://_, _+++https+++://_, _ws://_, _wss://_ schemas). | ||||||
| 
 | 
 | ||||||
| *json-rpc-shell* will use it to send any JSON-RPC 2.0 requests you enter on its | *json-rpc-shell* will use it to send any JSON-RPC 2.0 requests you enter on its | ||||||
| command line. The server's response will be parsed and validated, stripping it | command line. The server's response will be parsed and validated, stripping it | ||||||
| @ -77,15 +76,6 @@ Protocol | |||||||
| *-o* _ORIGIN_, *--origin*=_ORIGIN_:: | *-o* _ORIGIN_, *--origin*=_ORIGIN_:: | ||||||
| 	Set the HTTP Origin header to _ORIGIN_.  Some servers may need this. | 	Set the HTTP Origin header to _ORIGIN_.  Some servers may need this. | ||||||
| 
 | 
 | ||||||
| *-O*[__PATH__], *--openrpc*[**=**__PATH__]:: |  | ||||||
| 	Call "rpc.discover" upon start-up in order to pull in OpenRPC data for |  | ||||||
| 	tab completion of method names.  If a path is given, it is read from a file. |  | ||||||
| 
 |  | ||||||
| *-e*, *--execute*:: |  | ||||||
| 	Rather than an _ENDPOINT_, accept a command line to execute and communicate |  | ||||||
| 	with using the JSON-RPC 2.0 protocol variation used in the Language Server |  | ||||||
| 	Protocol. |  | ||||||
| 
 |  | ||||||
| Program information | Program information | ||||||
| ~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~ | ||||||
| *-h*, *--help*:: | *-h*, *--help*:: | ||||||
| @ -97,16 +87,8 @@ Program information | |||||||
| *--write-default-cfg*[**=**__PATH__]:: | *--write-default-cfg*[**=**__PATH__]:: | ||||||
| 	Write a default configuration file, show its path and exit. | 	Write a default configuration file, show its path and exit. | ||||||
| 
 | 
 | ||||||
| Environment |  | ||||||
| ----------- |  | ||||||
| *VISUAL*, *EDITOR*:: |  | ||||||
| 	The editor program to be launched by the M-e key binding. |  | ||||||
| 	If neither variable is set, it defaults to *vi*(1). |  | ||||||
| 
 |  | ||||||
| Files | Files | ||||||
| ----- | ----- | ||||||
| *json-rpc-shell* follows the XDG Base Directory Specification. |  | ||||||
| 
 |  | ||||||
| _~/.config/json-rpc-shell/json-rpc-shell.conf_:: | _~/.config/json-rpc-shell/json-rpc-shell.conf_:: | ||||||
| 	The configuration file, in which you can configure color output and | 	The configuration file, in which you can configure color output and | ||||||
| 	CA certificate paths.  Use the *--write-default-cfg* option to create | 	CA certificate paths.  Use the *--write-default-cfg* option to create | ||||||
| @ -125,13 +107,14 @@ requests, it is often convenient or even necessary to run a full text editor | |||||||
| in order to construct complex objects or arrays, and may even be used to import | in order to construct complex objects or arrays, and may even be used to import | ||||||
| data from elsewhere.  You can launch an editor for the current request using | data from elsewhere.  You can launch an editor for the current request using | ||||||
| the M-e key combination.  Both *readline*(3) and *editline*(7) also support | the M-e key combination.  Both *readline*(3) and *editline*(7) also support | ||||||
| multiline editing natively, press either M-Enter or C-v C-j in order to insert | multiline editing natively, though you need to press C-v C-j in order to insert | ||||||
| newlines. | newlines. | ||||||
| 
 | 
 | ||||||
| WebSocket | WebSockets | ||||||
| ~~~~~~~~~ | ~~~~~~~~~~ | ||||||
| The JSON-RPC 2.0 specification doesn't say almost anything about underlying | The JSON-RPC 2.0 specification doesn't say almost anything about underlying | ||||||
| transports.  The way it's implemented here is that every request is sent as | transports. As far as the author is aware, he is the only person combining it | ||||||
|  | with WebSockets.  The way it's implemented here is that every request is sent as | ||||||
| a single text message.  If it has an "id" field, i.e., it's not just | a single text message.  If it has an "id" field, i.e., it's not just | ||||||
| a notification, the client waits for a message from the server in response. | a notification, the client waits for a message from the server in response. | ||||||
| Should any message arrive unexpectedly, you will receive a warning. | Should any message arrive unexpectedly, you will receive a warning. | ||||||
| @ -141,7 +124,8 @@ the higher-level protocol (the "Sec-Ws-Protocol" HTTP field). | |||||||
| 
 | 
 | ||||||
| Bugs | Bugs | ||||||
| ---- | ---- | ||||||
| The editline (libedit) frontend may exhibit some unexpected behaviour. | The editline (libedit) frontend is more of a proof of concept that mostly seems | ||||||
|  | to work but exhibits bugs that are not our fault. | ||||||
| 
 | 
 | ||||||
| Examples | Examples | ||||||
| -------- | -------- | ||||||
|  | |||||||
							
								
								
									
										1516
									
								
								json-rpc-shell.c
									
									
									
									
									
								
							
							
						
						
									
										1516
									
								
								json-rpc-shell.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,7 +1,7 @@ | |||||||
| /*
 | /*
 | ||||||
|  * json-rpc-test-server.c: JSON-RPC 2.0 demo server |  * json-rpc-test-server.c: JSON-RPC 2.0 demo server | ||||||
|  * |  * | ||||||
|  * Copyright (c) 2015 - 2022, Přemysl Eric Janouch <p@janouch.name> |  * Copyright (c) 2015 - 2020, Přemysl Eric Janouch <p@janouch.name> | ||||||
|  * |  * | ||||||
|  * Permission to use, copy, modify, and/or distribute this software for any |  * Permission to use, copy, modify, and/or distribute this software for any | ||||||
|  * purpose with or without fee is hereby granted. |  * purpose with or without fee is hereby granted. | ||||||
| @ -329,7 +329,6 @@ fcgi_muxer_on_get_values | |||||||
| 	nv_parser.output = &values; | 	nv_parser.output = &values; | ||||||
| 
 | 
 | ||||||
| 	fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len); | 	fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len); | ||||||
| 	fcgi_nv_parser_free (&nv_parser); |  | ||||||
| 	const char *key = NULL; | 	const char *key = NULL; | ||||||
| 
 | 
 | ||||||
| 	// No real-world servers seem to actually use multiplexing
 | 	// No real-world servers seem to actually use multiplexing
 | ||||||
| @ -525,11 +524,11 @@ fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len) | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// @}
 | /// @}
 | ||||||
| // --- WebSocket ---------------------------------------------------------------
 | // --- WebSockets --------------------------------------------------------------
 | ||||||
| /// @defgroup WebSocket
 | /// @defgroup WebSockets
 | ||||||
| /// @{
 | /// @{
 | ||||||
| 
 | 
 | ||||||
| // WebSocket isn't CGI-compatible, therefore we must handle the initial HTTP
 | // WebSockets aren't CGI-compatible, therefore we must handle the initial HTTP
 | ||||||
| // handshake ourselves.  Luckily it's not too much of a bother with http-parser.
 | // handshake ourselves.  Luckily it's not too much of a bother with http-parser.
 | ||||||
| // Typically there will be a normal HTTP server in front of us, proxying the
 | // Typically there will be a normal HTTP server in front of us, proxying the
 | ||||||
| // requests based on the URI.
 | // requests based on the URI.
 | ||||||
| @ -537,7 +536,7 @@ fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len) | |||||||
| enum ws_handler_state | enum ws_handler_state | ||||||
| { | { | ||||||
| 	WS_HANDLER_CONNECTING,              ///< Parsing HTTP
 | 	WS_HANDLER_CONNECTING,              ///< Parsing HTTP
 | ||||||
| 	WS_HANDLER_OPEN,                    ///< Parsing WebSocket frames
 | 	WS_HANDLER_OPEN,                    ///< Parsing WebSockets frames
 | ||||||
| 	WS_HANDLER_CLOSING,                 ///< Partial closure by us
 | 	WS_HANDLER_CLOSING,                 ///< Partial closure by us
 | ||||||
| 	WS_HANDLER_FLUSHING,                ///< Just waiting for client EOF
 | 	WS_HANDLER_FLUSHING,                ///< Just waiting for client EOF
 | ||||||
| 	WS_HANDLER_CLOSED                   ///< Dead, both sides closed
 | 	WS_HANDLER_CLOSED                   ///< Dead, both sides closed
 | ||||||
| @ -851,17 +850,6 @@ ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents) | |||||||
| 	self->close_cb (self, false /* half_close */); | 	self->close_cb (self, false /* half_close */); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool ws_handler_fail_handshake (struct ws_handler *self, |  | ||||||
| 	const char *status, ...) ATTRIBUTE_SENTINEL; |  | ||||||
| 
 |  | ||||||
| #define HTTP_101_SWITCHING_PROTOCOLS    "101 Switching Protocols" |  | ||||||
| #define HTTP_400_BAD_REQUEST            "400 Bad Request" |  | ||||||
| #define HTTP_405_METHOD_NOT_ALLOWED     "405 Method Not Allowed" |  | ||||||
| #define HTTP_408_REQUEST_TIMEOUT        "408 Request Timeout" |  | ||||||
| #define HTTP_417_EXPECTATION_FAILED     "407 Expectation Failed" |  | ||||||
| #define HTTP_426_UPGRADE_REQUIRED       "426 Upgrade Required" |  | ||||||
| #define HTTP_505_VERSION_NOT_SUPPORTED  "505 HTTP Version Not Supported" |  | ||||||
| 
 |  | ||||||
| static void | static void | ||||||
| ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents) | ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents) | ||||||
| { | { | ||||||
| @ -869,7 +857,13 @@ ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents) | |||||||
| 	(void) revents; | 	(void) revents; | ||||||
| 	struct ws_handler *self = watcher->data; | 	struct ws_handler *self = watcher->data; | ||||||
| 
 | 
 | ||||||
| 	ws_handler_fail_handshake (self, HTTP_408_REQUEST_TIMEOUT, NULL); | 	// XXX: this is a no-op, since this currently doesn't even call shutdown
 | ||||||
|  | 	//   immediately but postpones it until later
 | ||||||
|  | 	self->close_cb (self, true /* half_close */); | ||||||
|  | 	self->state = WS_HANDLER_FLUSHING; | ||||||
|  | 
 | ||||||
|  | 	if (self->on_close) | ||||||
|  | 		self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "handshake timeout"); | ||||||
| 
 | 
 | ||||||
| 	self->state = WS_HANDLER_CLOSED; | 	self->state = WS_HANDLER_CLOSED; | ||||||
| 	self->close_cb (self, false /* half_close */); | 	self->close_cb (self, false /* half_close */); | ||||||
| @ -1008,10 +1002,9 @@ ws_handler_on_headers_complete (http_parser *parser) | |||||||
| 	if (self->have_header_value) | 	if (self->have_header_value) | ||||||
| 		ws_handler_on_header_read (self); | 		ws_handler_on_header_read (self); | ||||||
| 
 | 
 | ||||||
| 	// We require a protocol upgrade.  1 is for "skip body", 2 is the same
 | 	// We strictly require a protocol upgrade
 | ||||||
| 	// + "stop processing", return another number to indicate a problem here.
 |  | ||||||
| 	if (!parser->upgrade) | 	if (!parser->upgrade) | ||||||
| 		return 3; | 		return 2; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| @ -1020,10 +1013,17 @@ static int | |||||||
| ws_handler_on_url (http_parser *parser, const char *at, size_t len) | ws_handler_on_url (http_parser *parser, const char *at, size_t len) | ||||||
| { | { | ||||||
| 	struct ws_handler *self = parser->data; | 	struct ws_handler *self = parser->data; | ||||||
| 	str_append_data (&self->url, at, len); | 	str_append_data (&self->value, at, len); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #define HTTP_101_SWITCHING_PROTOCOLS    "101 Switching Protocols" | ||||||
|  | #define HTTP_400_BAD_REQUEST            "400 Bad Request" | ||||||
|  | #define HTTP_405_METHOD_NOT_ALLOWED     "405 Method Not Allowed" | ||||||
|  | #define HTTP_417_EXPECTATION_FAILED     "407 Expectation Failed" | ||||||
|  | #define HTTP_426_UPGRADE_REQUIRED       "426 Upgrade Required" | ||||||
|  | #define HTTP_505_VERSION_NOT_SUPPORTED  "505 HTTP Version Not Supported" | ||||||
|  | 
 | ||||||
| static void | static void | ||||||
| ws_handler_http_responsev (struct ws_handler *self, | ws_handler_http_responsev (struct ws_handler *self, | ||||||
| 	const char *status, char *const *fields) | 	const char *status, char *const *fields) | ||||||
| @ -1065,7 +1065,6 @@ ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...) | |||||||
| 	struct strv v = strv_make (); | 	struct strv v = strv_make (); | ||||||
| 	while ((s = va_arg (ap, const char *))) | 	while ((s = va_arg (ap, const char *))) | ||||||
| 		strv_append (&v, s); | 		strv_append (&v, s); | ||||||
| 	strv_append (&v, "Connection: close"); |  | ||||||
| 
 | 
 | ||||||
| 	va_end (ap); | 	va_end (ap); | ||||||
| 	ws_handler_http_responsev (self, status, v.vector); | 	ws_handler_http_responsev (self, status, v.vector); | ||||||
| @ -1110,7 +1109,7 @@ ws_handler_finish_handshake (struct ws_handler *self) | |||||||
| 	if (!connection || strcasecmp_ascii (connection, "Upgrade")) | 	if (!connection || strcasecmp_ascii (connection, "Upgrade")) | ||||||
| 		FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); | 		FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); | ||||||
| 
 | 
 | ||||||
| 	// Check if we can actually upgrade the protocol to WebSocket
 | 	// Check if we can actually upgrade the protocol to WebSockets
 | ||||||
| 	const char *upgrade = str_map_find (&self->headers, "Upgrade"); | 	const char *upgrade = str_map_find (&self->headers, "Upgrade"); | ||||||
| 	struct http_protocol *offered_upgrades = NULL; | 	struct http_protocol *offered_upgrades = NULL; | ||||||
| 	bool can_upgrade = false; | 	bool can_upgrade = false; | ||||||
| @ -1268,13 +1267,11 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len) | |||||||
| 		ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher); | 		ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher); | ||||||
| 
 | 
 | ||||||
| 		if (err == HPE_CB_headers_complete) | 		if (err == HPE_CB_headers_complete) | ||||||
| 		{ |  | ||||||
| 			print_debug ("WS handshake failed: %s", "missing `Upgrade' field"); | 			print_debug ("WS handshake failed: %s", "missing `Upgrade' field"); | ||||||
| 			FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED, | 		else | ||||||
| 				"Upgrade: websocket", SEC_WS_VERSION ": 13"); | 			print_debug ("WS handshake failed: %s", | ||||||
| 		} | 				http_errno_description (err)); | ||||||
| 
 | 
 | ||||||
| 		print_debug ("WS handshake failed: %s", http_errno_description (err)); |  | ||||||
| 		FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); | 		FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); | ||||||
| 	} | 	} | ||||||
| 	return true; | 	return true; | ||||||
| @ -1288,7 +1285,7 @@ static struct simple_config_item g_config_table[] = | |||||||
| 	{ "bind_host",       NULL,              "Address of the server"          }, | 	{ "bind_host",       NULL,              "Address of the server"          }, | ||||||
| 	{ "port_fastcgi",    "9000",            "Port to bind for FastCGI"       }, | 	{ "port_fastcgi",    "9000",            "Port to bind for FastCGI"       }, | ||||||
| 	{ "port_scgi",       NULL,              "Port to bind for SCGI"          }, | 	{ "port_scgi",       NULL,              "Port to bind for SCGI"          }, | ||||||
| 	{ "port_ws",         NULL,              "Port to bind for WebSocket"     }, | 	{ "port_ws",         NULL,              "Port to bind for WebSockets"    }, | ||||||
| 	{ "pid_file",        NULL,              "Full path for the PID file"     }, | 	{ "pid_file",        NULL,              "Full path for the PID file"     }, | ||||||
| 	// XXX: here belongs something like a web SPA that interfaces with us
 | 	// XXX: here belongs something like a web SPA that interfaces with us
 | ||||||
| 	{ "static_root",     NULL,              "The root for static content"    }, | 	{ "static_root",     NULL,              "The root for static content"    }, | ||||||
| @ -1448,39 +1445,6 @@ json_rpc_handler_info_cmp (const void *first, const void *second) | |||||||
| 		((struct json_rpc_handler_info *) second)->method_name); | 		((struct json_rpc_handler_info *) second)->method_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static json_t * |  | ||||||
| open_rpc_describe (const char *method, json_t *result) |  | ||||||
| { |  | ||||||
| 	return json_pack ("{sssoso}", "name", method, "params", json_pack ("[]"), |  | ||||||
| 		"result", json_pack ("{ssso}", "name", method, "schema", result)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // This server rarely sees changes and we can afford to hardcode the schema
 |  | ||||||
| static json_t * |  | ||||||
| json_rpc_discover (struct server_context *ctx, json_t *params) |  | ||||||
| { |  | ||||||
| 	(void) ctx; |  | ||||||
| 	(void) params; |  | ||||||
| 
 |  | ||||||
| 	json_t *info = json_pack ("{ssss}", |  | ||||||
| 		"title", PROGRAM_NAME, "version", PROGRAM_VERSION); |  | ||||||
| 	json_t *methods = json_pack ("[oooo]", |  | ||||||
| 		open_rpc_describe ("date", json_pack ("{ssso}", "type", "object", |  | ||||||
| 			"properties", json_pack ("{s{ss}s{ss}s{ss}s{ss}s{ss}s{ss}}", |  | ||||||
| 				"year",    "type", "number", |  | ||||||
| 				"month",   "type", "number", |  | ||||||
| 				"day",     "type", "number", |  | ||||||
| 				"hours",   "type", "number", |  | ||||||
| 				"minutes", "type", "number", |  | ||||||
| 				"seconds", "type", "number"))), |  | ||||||
| 		open_rpc_describe ("ping", json_pack ("{ss}", "type", "string")), |  | ||||||
| 		open_rpc_describe ("rpc.discover", json_pack ("{ss}", "$ref", |  | ||||||
| 			"https://github.com/open-rpc/meta-schema/raw/master/schema.json")), |  | ||||||
| 		open_rpc_describe ("wait", json_pack ("{ss}", "type", "null"))); |  | ||||||
| 	return json_rpc_response (NULL, json_pack ("{sssoso}", |  | ||||||
| 		"openrpc", "1.2.6", "info", info, "methods", methods), NULL); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static json_t * | static json_t * | ||||||
| json_rpc_ping (struct server_context *ctx, json_t *params) | json_rpc_ping (struct server_context *ctx, json_t *params) | ||||||
| { | { | ||||||
| @ -1493,16 +1457,6 @@ json_rpc_ping (struct server_context *ctx, json_t *params) | |||||||
| 	return json_rpc_response (NULL, json_string ("pong"), NULL); | 	return json_rpc_response (NULL, json_string ("pong"), NULL); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static json_t * |  | ||||||
| json_rpc_wait (struct server_context *ctx, json_t *params) |  | ||||||
| { |  | ||||||
| 	(void) ctx; |  | ||||||
| 	(void) params; |  | ||||||
| 
 |  | ||||||
| 	sleep (1); |  | ||||||
| 	return json_rpc_response (NULL, json_null (), NULL); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static json_t * | static json_t * | ||||||
| json_rpc_date (struct server_context *ctx, json_t *params) | json_rpc_date (struct server_context *ctx, json_t *params) | ||||||
| { | { | ||||||
| @ -1532,10 +1486,8 @@ process_json_rpc_request (struct server_context *ctx, json_t *request) | |||||||
| 	// Eventually it might be better to move this into a map in the context.
 | 	// Eventually it might be better to move this into a map in the context.
 | ||||||
| 	static struct json_rpc_handler_info handlers[] = | 	static struct json_rpc_handler_info handlers[] = | ||||||
| 	{ | 	{ | ||||||
| 		{ "date",         json_rpc_date     }, | 		{ "date", json_rpc_date }, | ||||||
| 		{ "ping",         json_rpc_ping     }, | 		{ "ping", json_rpc_ping }, | ||||||
| 		{ "rpc.discover", json_rpc_discover }, |  | ||||||
| 		{ "wait",         json_rpc_wait     }, |  | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	if (!json_is_object (request)) | 	if (!json_is_object (request)) | ||||||
| @ -1592,6 +1544,7 @@ static void | |||||||
| process_json_rpc (struct server_context *ctx, | process_json_rpc (struct server_context *ctx, | ||||||
| 	const void *data, size_t len, struct str *output) | 	const void *data, size_t len, struct str *output) | ||||||
| { | { | ||||||
|  | 
 | ||||||
| 	json_error_t e; | 	json_error_t e; | ||||||
| 	json_t *request; | 	json_t *request; | ||||||
| 	if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e))) | 	if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e))) | ||||||
| @ -1666,37 +1619,15 @@ struct request_handler | |||||||
| 	LIST_HEADER (struct request_handler) | 	LIST_HEADER (struct request_handler) | ||||||
| 
 | 
 | ||||||
| 	/// Install ourselves as the handler for the request, if applicable.
 | 	/// Install ourselves as the handler for the request, if applicable.
 | ||||||
| 	/// If the request contains data, check it against CONTENT_LENGTH.
 |  | ||||||
| 	/// ("Transfer-Encoding: chunked" should be dechunked by the HTTP server,
 |  | ||||||
| 	/// however it is possible that it mishandles this situation.)
 |  | ||||||
| 	/// Sets @a continue_ to false if further processing should be stopped,
 | 	/// Sets @a continue_ to false if further processing should be stopped,
 | ||||||
| 	/// meaning the request has already been handled.
 | 	/// meaning the request has already been handled.
 | ||||||
| 	/// Note that starting the response before receiving all data denies you
 |  | ||||||
| 	/// the option of returning error status codes based on the data.
 |  | ||||||
| 	bool (*try_handle) (struct request *request, | 	bool (*try_handle) (struct request *request, | ||||||
| 		struct str_map *headers, bool *continue_); | 		struct str_map *headers, bool *continue_); | ||||||
| 
 | 
 | ||||||
| 	/// Handle incoming data.  "len == 0" means EOF.
 | 	/// Handle incoming data.  "len == 0" means EOF.
 | ||||||
| 	/// Returns false if there is no more processing to be done.
 | 	/// Returns false if there is no more processing to be done.
 | ||||||
| 	/// EOF is never delivered on a network error (see client_read_loop()).
 | 	// FIXME: the EOF may or may not be delivered when request is cut short,
 | ||||||
| 	// XXX: the EOF may or may not be delivered when the request is cut short:
 | 	//   we should fix FastCGI not to deliver it on CONTENT_LENGTH mismatch
 | ||||||
| 	//  - client_scgi delivers an EOF when it itself receives an EOF without
 |  | ||||||
| 	//    considering any mismatch, and it can deliver another one earlier
 |  | ||||||
| 	//    when the counter just goes down to 0... depends on what we return
 |  | ||||||
| 	//    from here upon the first occasion (whether we want to close).
 |  | ||||||
| 	//  - FCGI_ABORT_REQUEST /might/ not close the stdin and it /might/ cover
 |  | ||||||
| 	//    a CONTENT_LENGTH mismatch, since this callback wouldn't get invoked.
 |  | ||||||
| 	//    The FastCGI specification explicitly says to compare CONTENT_LENGTH
 |  | ||||||
| 	//    against the number of received bytes, which may only be smaller.
 |  | ||||||
| 	//
 |  | ||||||
| 	// We might want to adjust client_scgi and client_fcgi to not invoke
 |  | ||||||
| 	// request_push(EOF) when CONTENT_LENGTH hasn't been reached and remove
 |  | ||||||
| 	// the extra EOF generation from client_scgi (why is it there, does the
 |  | ||||||
| 	// server keep the connection open, or is it just a precaution?)
 |  | ||||||
| 	//
 |  | ||||||
| 	// The finalization callback takes care of any needs to destruct data.
 |  | ||||||
| 	// If we handle this reliably in all clients, try_handle won't have to,
 |  | ||||||
| 	// as it will run in a stricter-than-CGI scenario.
 |  | ||||||
| 	bool (*push_cb) (struct request *request, const void *data, size_t len); | 	bool (*push_cb) (struct request *request, const void *data, size_t len); | ||||||
| 
 | 
 | ||||||
| 	/// Destroy the handler's data stored in the request object
 | 	/// Destroy the handler's data stored in the request object
 | ||||||
| @ -1818,9 +1749,7 @@ request_handler_json_rpc_push | |||||||
| 
 | 
 | ||||||
| 	// TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
 | 	// TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
 | ||||||
| 	//   client hasn't been successful in transferring all of its data.
 | 	//   client hasn't been successful in transferring all of its data.
 | ||||||
| 	//   See also comment on request_handler::push_cb.  For JSON-RPC, though,
 | 	//   See also comment on request_handler::push_cb.
 | ||||||
| 	//   it shouldn't matter as an incomplete request will be invalid and
 |  | ||||||
| 	//   clients have no reason to append unnecessary trailing bytes.
 |  | ||||||
| 
 | 
 | ||||||
| 	struct str response = str_make (); | 	struct str response = str_make (); | ||||||
| 	str_append (&response, "Status: 200 OK\n"); | 	str_append (&response, "Status: 200 OK\n"); | ||||||
| @ -1937,13 +1866,8 @@ request_handler_static_try_handle | |||||||
| 	char *path = xstrdup_printf ("%s%s", root, suffix); | 	char *path = xstrdup_printf ("%s%s", root, suffix); | ||||||
| 	print_debug ("trying to statically serve %s", path); | 	print_debug ("trying to statically serve %s", path); | ||||||
| 
 | 
 | ||||||
|  | 	// TODO: check that this is a regular file
 | ||||||
| 	FILE *fp = fopen (path, "rb"); | 	FILE *fp = fopen (path, "rb"); | ||||||
| 	struct stat st = {}; |  | ||||||
| 	if (fp && !fstat (fileno (fp), &st) && !S_ISREG (st.st_mode)) |  | ||||||
| 	{ |  | ||||||
| 		fclose (fp); |  | ||||||
| 		fp = NULL; |  | ||||||
| 	} |  | ||||||
| 	if (!fp) | 	if (!fp) | ||||||
| 	{ | 	{ | ||||||
| 		struct str response = str_make (); | 		struct str response = str_make (); | ||||||
| @ -1988,8 +1912,8 @@ request_handler_static_try_handle | |||||||
| 		request_write (request, buf, len); | 		request_write (request, buf, len); | ||||||
| 	fclose (fp); | 	fclose (fp); | ||||||
| 
 | 
 | ||||||
| 	// TODO: this should rather not be returned all at once but in chunks
 | 	// TODO: this should rather not be returned all at once but in chunks;
 | ||||||
| 	//   (consider Transfer-Encoding); file read requests never return EAGAIN
 | 	//   file read requests never return EAGAIN
 | ||||||
| 	// TODO: actual file data should really be returned by a callback when
 | 	// TODO: actual file data should really be returned by a callback when
 | ||||||
| 	//   the socket is writable with nothing to be sent (pumping the entire
 | 	//   the socket is writable with nothing to be sent (pumping the entire
 | ||||||
| 	//   file all at once won't really work if it's huge).
 | 	//   file all at once won't really work if it's huge).
 | ||||||
| @ -2123,8 +2047,6 @@ static void | |||||||
| client_shutdown (struct client *self) | client_shutdown (struct client *self) | ||||||
| { | { | ||||||
| 	self->flushing = true; | 	self->flushing = true; | ||||||
| 	// In case this shutdown is immediately followed by a close, try our best
 |  | ||||||
| 	(void) flush_queue (&self->write_queue, self->socket_fd); |  | ||||||
| 	ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE); | 	ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -2436,15 +2358,14 @@ client_scgi_on_content (void *user_data, const void *data, size_t len) | |||||||
| 		print_debug ("SCGI request got more data than CONTENT_LENGTH"); | 		print_debug ("SCGI request got more data than CONTENT_LENGTH"); | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 	// We're in a slight disagreement with the SCGI specification since
 | 	// We're in a slight disagreement with the specification since
 | ||||||
| 	// this tries to write output before it has read all the input
 | 	// this tries to write output before it has read all the input
 | ||||||
| 	if (!request_push (&self->request, data, len)) | 	if (!request_push (&self->request, data, len)) | ||||||
| 		return false; | 		return false; | ||||||
| 	if ((self->remaining_content -= len)) |  | ||||||
| 		return true; |  | ||||||
| 
 | 
 | ||||||
| 	// Signalise end of input to the request handler
 | 	// Signalise end of input to the request handler
 | ||||||
| 	return request_push (&self->request, NULL, 0); | 	return (self->remaining_content -= len) != 0 | ||||||
|  | 		|| request_push (&self->request, NULL, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
| @ -2497,12 +2418,12 @@ client_scgi_create (EV_P_ int sock_fd) | |||||||
| 	return &self->client; | 	return &self->client; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // --- WebSocket client handler ------------------------------------------------
 | // --- WebSockets client handler -----------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| struct client_ws | struct client_ws | ||||||
| { | { | ||||||
| 	struct client client;               ///< Parent class
 | 	struct client client;               ///< Parent class
 | ||||||
| 	struct ws_handler handler;          ///< WebSocket connection handler
 | 	struct ws_handler handler;          ///< WebSockets connection handler
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static bool | static bool | ||||||
| @ -2593,165 +2514,6 @@ client_ws_create (EV_P_ int sock_fd) | |||||||
| 	return &self->client; | 	return &self->client; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // --- Co-process client -------------------------------------------------------
 |  | ||||||
| 
 |  | ||||||
| // This is mostly copied over from json-rpc-shell.c, only a bit simplified.
 |  | ||||||
| // We're giving up on header parsing in order to keep this small.
 |  | ||||||
| struct co_context |  | ||||||
| { |  | ||||||
| 	struct server_context *ctx;         ///< Server context
 |  | ||||||
| 	struct str message;                 ///< Message data
 |  | ||||||
| 	struct http_parser parser;          ///< HTTP parser
 |  | ||||||
| 	bool pending_fake_starter;          ///< Start of message?
 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static int |  | ||||||
| client_co_on_message_begin (http_parser *parser) |  | ||||||
| { |  | ||||||
| 	struct co_context *self = parser->data; |  | ||||||
| 	str_reset (&self->message); |  | ||||||
| 	return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static int |  | ||||||
| client_co_on_body (http_parser *parser, const char *at, size_t len) |  | ||||||
| { |  | ||||||
| 	struct co_context *self = parser->data; |  | ||||||
| 	str_append_data (&self->message, at, len); |  | ||||||
| 	return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static int |  | ||||||
| client_co_on_message_complete (http_parser *parser) |  | ||||||
| { |  | ||||||
| 	struct co_context *self = parser->data; |  | ||||||
| 	http_parser_pause (&self->parser, true); |  | ||||||
| 	return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // The LSP incorporates a very thin subset of RFC 822, and it so happens
 |  | ||||||
| // that we may simply reuse the full HTTP parser here, with a small hack.
 |  | ||||||
| static const http_parser_settings client_co_http_settings = |  | ||||||
| { |  | ||||||
| 	.on_message_begin    = client_co_on_message_begin, |  | ||||||
| 	.on_body             = client_co_on_body, |  | ||||||
| 	.on_message_complete = client_co_on_message_complete, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| client_co_respond (const struct str *buf) |  | ||||||
| { |  | ||||||
| 	struct str wrapped = str_make(); |  | ||||||
| 	str_append_printf (&wrapped, |  | ||||||
| 		"Content-Length: %zu\r\n" |  | ||||||
| 		"Content-Type: application/json; charset=utf-8\r\n" |  | ||||||
| 		"\r\n", buf->len); |  | ||||||
| 	str_append_data (&wrapped, buf->str, buf->len); |  | ||||||
| 
 |  | ||||||
| 	if (write (STDOUT_FILENO, wrapped.str, wrapped.len) |  | ||||||
| 		!= (ssize_t) wrapped.len) |  | ||||||
| 		exit_fatal ("write: %s", strerror (errno)); |  | ||||||
| 	str_free (&wrapped); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| client_co_inject_starter (struct co_context *self) |  | ||||||
| { |  | ||||||
| 	// The default "Connection: keep-alive" maps well here.
 |  | ||||||
| 	// We cannot feed this line into the parser from within callbacks.
 |  | ||||||
| 	static const char starter[] = "POST / HTTP/1.1\r\n"; |  | ||||||
| 	http_parser_pause (&self->parser, false); |  | ||||||
| 
 |  | ||||||
| 	size_t n_parsed = http_parser_execute (&self->parser, |  | ||||||
| 		&client_co_http_settings, starter, sizeof starter - 1); |  | ||||||
| 	enum http_errno err = HTTP_PARSER_ERRNO (&self->parser); |  | ||||||
| 	if (n_parsed != sizeof starter - 1 || err != HPE_OK) |  | ||||||
| 		exit_fatal ("protocol failure: %s", http_errno_description (err)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| client_co_process (struct co_context *self) |  | ||||||
| { |  | ||||||
| 	struct str *message = &self->message; |  | ||||||
| 	struct str response = str_make (); |  | ||||||
| 	process_json_rpc (self->ctx, message->str, message->len, &response); |  | ||||||
| 	if (response.len) |  | ||||||
| 		client_co_respond (&response); |  | ||||||
| 	str_free (&response); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| client_co_parse (struct co_context *self, const char *data, size_t len, |  | ||||||
| 	size_t *n_parsed) |  | ||||||
| { |  | ||||||
| 	if (self->pending_fake_starter) |  | ||||||
| 	{ |  | ||||||
| 		self->pending_fake_starter = false; |  | ||||||
| 		client_co_inject_starter (self); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	*n_parsed = http_parser_execute |  | ||||||
| 		(&self->parser, &client_co_http_settings, data, len); |  | ||||||
| 	if (self->parser.upgrade) |  | ||||||
| 		exit_fatal ("protocol failure: %s", "unsupported upgrade attempt"); |  | ||||||
| 
 |  | ||||||
| 	enum http_errno err = HTTP_PARSER_ERRNO (&self->parser); |  | ||||||
| 	if (err == HPE_PAUSED) |  | ||||||
| 	{ |  | ||||||
| 		self->pending_fake_starter = true; |  | ||||||
| 		client_co_process (self); |  | ||||||
| 	} |  | ||||||
| 	else if (err != HPE_OK) |  | ||||||
| 		exit_fatal ("protocol failure: %s", http_errno_description (err)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| client_co_on_data (struct co_context *self, const char *data, size_t len) |  | ||||||
| { |  | ||||||
| 	size_t n_parsed = 0; |  | ||||||
| 	do |  | ||||||
| 	{ |  | ||||||
| 		client_co_parse (self, data, len, &n_parsed); |  | ||||||
| 		data += n_parsed; |  | ||||||
| 	} |  | ||||||
| 	while ((len -= n_parsed)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| client_co_run (struct server_context *ctx) |  | ||||||
| { |  | ||||||
| 	struct co_context self = {}; |  | ||||||
| 	self.ctx = ctx; |  | ||||||
| 	self.message = str_make (); |  | ||||||
| 	http_parser_init (&self.parser, HTTP_REQUEST); |  | ||||||
| 	self.parser.data = &self; |  | ||||||
| 	self.pending_fake_starter = true; |  | ||||||
| 
 |  | ||||||
| 	hard_assert (set_blocking (STDIN_FILENO, false)); |  | ||||||
| 	struct str buf = str_make (); |  | ||||||
| 	struct pollfd pfd = { .fd = STDIN_FILENO, .events = POLLIN }; |  | ||||||
| 	while (true) |  | ||||||
| 	{ |  | ||||||
| 		if (poll (&pfd, 1, -1) <= 0) |  | ||||||
| 			exit_fatal ("poll: %s", strerror (errno)); |  | ||||||
| 
 |  | ||||||
| 		str_remove_slice (&buf, 0, buf.len); |  | ||||||
| 		enum socket_io_result result = socket_io_try_read (pfd.fd, &buf); |  | ||||||
| 		int errno_saved = errno; |  | ||||||
| 
 |  | ||||||
| 		if (buf.len) |  | ||||||
| 			client_co_on_data (&self, buf.str, buf.len); |  | ||||||
| 		if (result == SOCKET_IO_ERROR) |  | ||||||
| 			exit_fatal ("read: %s", strerror (errno_saved)); |  | ||||||
| 		if (result == SOCKET_IO_EOF) |  | ||||||
| 			break; |  | ||||||
| 	} |  | ||||||
| 	str_free (&buf); |  | ||||||
| 	str_free (&self.message); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // --- Basic server stuff ------------------------------------------------------
 | // --- Basic server stuff ------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| typedef struct client *(*client_create_fn) (EV_P_ int sock_fd); | typedef struct client *(*client_create_fn) (EV_P_ int sock_fd); | ||||||
| @ -3115,12 +2877,11 @@ daemonize (struct server_context *ctx) | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static void | ||||||
| parse_program_arguments (int argc, char **argv, bool *running_as_slave) | parse_program_arguments (int argc, char **argv) | ||||||
| { | { | ||||||
| 	static const struct opt opts[] = | 	static const struct opt opts[] = | ||||||
| 	{ | 	{ | ||||||
| 		{ 't', "test", NULL, 0, "self-test" }, | 		{ 't', "test", NULL, 0, "self-test" }, | ||||||
| 		{ 's', "slave", NULL, 0, "co-process mode" }, |  | ||||||
| 		{ 'd', "debug", NULL, 0, "run in debug mode" }, | 		{ 'd', "debug", NULL, 0, "run in debug mode" }, | ||||||
| 		{ 'h', "help", NULL, 0, "display this help and exit" }, | 		{ 'h', "help", NULL, 0, "display this help and exit" }, | ||||||
| 		{ 'V', "version", NULL, 0, "output version information and exit" }, | 		{ 'V', "version", NULL, 0, "output version information and exit" }, | ||||||
| @ -3140,9 +2901,6 @@ parse_program_arguments (int argc, char **argv, bool *running_as_slave) | |||||||
| 	case 't': | 	case 't': | ||||||
| 		test_main (argc, argv); | 		test_main (argc, argv); | ||||||
| 		exit (EXIT_SUCCESS); | 		exit (EXIT_SUCCESS); | ||||||
| 	case 's': |  | ||||||
| 		*running_as_slave = true; |  | ||||||
| 		break; |  | ||||||
| 	case 'd': | 	case 'd': | ||||||
| 		g_debug_mode = true; | 		g_debug_mode = true; | ||||||
| 		break; | 		break; | ||||||
| @ -3175,8 +2933,7 @@ parse_program_arguments (int argc, char **argv, bool *running_as_slave) | |||||||
| int | int | ||||||
| main (int argc, char *argv[]) | main (int argc, char *argv[]) | ||||||
| { | { | ||||||
| 	bool running_as_a_slave = false; | 	parse_program_arguments (argc, argv); | ||||||
| 	parse_program_arguments (argc, argv, &running_as_a_slave); |  | ||||||
| 
 | 
 | ||||||
| 	print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting"); | 	print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting"); | ||||||
| 
 | 
 | ||||||
| @ -3191,15 +2948,6 @@ main (int argc, char *argv[]) | |||||||
| 		exit (EXIT_FAILURE); | 		exit (EXIT_FAILURE); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// There's a lot of unnecessary left-over scaffolding in this program,
 |  | ||||||
| 	// for testing purposes assume that everything is synchronous
 |  | ||||||
| 	if (running_as_a_slave) |  | ||||||
| 	{ |  | ||||||
| 		client_co_run (&ctx); |  | ||||||
| 		server_context_free (&ctx); |  | ||||||
| 		return EXIT_SUCCESS; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	struct ev_loop *loop; | 	struct ev_loop *loop; | ||||||
| 	if (!(loop = EV_DEFAULT)) | 	if (!(loop = EV_DEFAULT)) | ||||||
| 		exit_fatal ("libev initialization failed"); | 		exit_fatal ("libev initialization failed"); | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								liberty
									
									
									
									
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								liberty
									
									
									
									
									
								
							| @ -1 +1 @@ | |||||||
| Subproject commit 1930f138d4836f8ed9613a17bfe09dc53441618a | Subproject commit 1a76b2032e6d18d9f95d9d0bb98edc26023c8618 | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user