├── test ├── __init__.py ├── test_skeeter_notifyer.sh ├── test_skeeter_subscriber.sh ├── config.py ├── test_skeeter_notifyer.py └── test_skeeter_subscriber.py ├── .gitignore ├── src ├── signal_handler.h ├── display_strings.h ├── command_line.h ├── message.h ├── zmq_shim.h ├── command_line.c ├── config.h ├── signal_handler.c ├── state.h ├── display_strings.c ├── message.c ├── state.c ├── dbg.h ├── dbg_syslog.h ├── config.c ├── bstrlib.h ├── main.c └── bstrlib.c ├── Makefile ├── skeeterrc └── README.md /test/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | __init__.py 4 | """ 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | 9 | # Compiled Static libraries 10 | *.lai 11 | *.la 12 | *.a 13 | 14 | __pycache__/* 15 | test/__pycache__/* 16 | 17 | # Compiled program 18 | skeeter 19 | -------------------------------------------------------------------------------- /test/test_skeeter_notifyer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # simple script to run the python test_skeeter_notifier 4 | 5 | set -x 6 | set -e 7 | 8 | PYTHON="python3.2" 9 | SKEETER="${HOME}/skeeter" 10 | export PYTHONPATH="${SKEETER}" 11 | $PYTHON "${SKEETER}/test/test_skeeter_notifyer.py" $1 12 | 13 | RETVAL=$? 14 | if [ $RETVAL -eq 0 ]; then 15 | echo Success 16 | exit 0 17 | fi 18 | 19 | echo Failure 20 | exit $? 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/test_skeeter_subscriber.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # simple script to run the python test_skeeter_subscriber prototype 4 | 5 | set -x 6 | set -e 7 | 8 | PYTHON="python3.2" 9 | SKEETER="${HOME}/skeeter" 10 | export PYTHONPATH="${SKEETER}" 11 | $PYTHON "${SKEETER}/test/test_skeeter_subscriber.py" $1 12 | 13 | RETVAL=$? 14 | if [ $RETVAL -eq 0 ]; then 15 | echo Success 16 | exit 0 17 | fi 18 | 19 | echo Failure 20 | exit $? 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/signal_handler.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * signal_handler.h 3 | * create and install a signal handler to set halt_signal to 1 on 4 | * SIGKILL or SIGTERM 5 | * 6 | *--------------------------------------------------------------------------*/ 7 | #if !defined(__SIGNAL_HANDLER_H__) 8 | #define __SIGNAL_HANDLER_H__ 9 | #include 10 | 11 | extern bool halt_signal; 12 | 13 | int 14 | install_signal_handler(); 15 | 16 | #endif // !defined(__SIGNAL_HANDLER_H__) 17 | -------------------------------------------------------------------------------- /src/display_strings.h: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------- 2 | // display_strings.h 3 | // 4 | // string values of constants used for debugging and information display 5 | //---------------------------------------------------------------------------- 6 | #if !defined(__DISPLAY_STRINGS_H__) 7 | #define __DISPLAY_STRINGS_H__ 8 | 9 | // strings for postgres ConnStatusType 10 | extern const char * CONN_STATUS[]; 11 | 12 | // strings for PostgresPollingStatusType 13 | extern const char * POLLING_STATUS[]; 14 | 15 | extern const char * EXEC_STATUS[]; 16 | #endif // !defined(__DISPLAY_STRINGS_H__) 17 | -------------------------------------------------------------------------------- /src/command_line.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * command_line.h 3 | * 4 | * parse the command line 5 | *--------------------------------------------------------------------------*/ 6 | #if !defined(__COMMAND_LINE_H__) 7 | #define __COMMAND_LINE_H__ 8 | 9 | #include "bstrlib.h" 10 | 11 | 12 | // sets config_path from -c or --config-path 13 | // config_path unchanged if one of these arguments is not present 14 | // returns 0 for success, -1 for failure 15 | extern int 16 | parse_command_line(int argc, char ** argv, bstring * config_path); 17 | 18 | #endif // !defined(__COMMAND_LINE_H__) 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # get the Postgresql libpq locations in a portable way 2 | PG_CONFIG = pg_config 3 | PG_INCLUDEDIR := $(shell $(PG_CONFIG) --includedir) 4 | PG_LIBDIR := $(shell $(PG_CONFIG) --libdir) 5 | 6 | CFLAGS=-g -O2 -Wall -Wextra -Isrc -I$(PG_INCLUDEDIR) -DNDEBUG $(OPTFLAGS) 7 | 8 | SOURCES=$(wildcard src/*.c) 9 | OBJECTS=$(patsubst %.c,%.o,$(SOURCES)) 10 | 11 | TARGET=skeeter 12 | 13 | all: $(TARGET) 14 | 15 | skeeter: $(OBJECTS) 16 | $(CC) -o $(TARGET) $(OBJECTS) -L$(PG_LIBDIR) $(OPTFLAGS) -lzmq -lpq 17 | 18 | dev: CFLAGS=-g -Wall -Isrc -I$(PG_INCLUDEDIR) -Wall -Wextra $(OPTFLAGS) 19 | dev: all 20 | 21 | clean: 22 | rm -f $(OBJECTS) 23 | rm -f $(TARGET) 24 | 25 | -------------------------------------------------------------------------------- /src/message.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * message.h 3 | * 4 | * publish a zeromq message 5 | *--------------------------------------------------------------------------*/ 6 | #if !defined(__MESSAGE__H__) 7 | #define __MESSAGE__H__ 8 | 9 | #include "bstrlib.h" 10 | 11 | // send a (possibly multipart) message over the pub socket 12 | // all stringws are copied to zmq message structures 13 | // it is the responsibility of the caller to clean up message_list 14 | // return 0 for success, -1 for failure 15 | int 16 | publish_message(const struct bstrList * message_list, void * zmq_pub_socket); 17 | 18 | #endif // !defined(__MESSAGE__H__) 19 | -------------------------------------------------------------------------------- /src/zmq_shim.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * zmq_shim.h 3 | * 4 | * http://zguide.zeromq.org/page:all#Suggested-Shim-Macros 5 | *--------------------------------------------------------------------------*/ 6 | #if !defined(__ZMQ_SHIM_H__) 7 | #define __ZMQ_SHIM_H__ 8 | 9 | #include 10 | 11 | #if ZMQ_VERSION_MAJOR == 2 12 | # define zmq_msg_send(msg,sock,opt) zmq_send (sock, msg, opt) 13 | # define zmq_msg_recv(msg,sock,opt) zmq_recv (sock, msg, opt) 14 | # define zmq_ctx_destroy(context) zmq_term(context) 15 | # define ZMQ_POLL_MSEC 1000 // zmq_poll is usec 16 | # define ZMQ_SNDHWM ZMQ_HWM 17 | # define ZMQ_RCVHWM ZMQ_HWM 18 | #elif ZMQ_VERSION_MAJOR == 3 19 | # define ZMQ_POLL_MSEC 1 // zmq_poll is msec 20 | #endif 21 | 22 | #ifndef ZMQ_DONTWAIT 23 | # define ZMQ_DONTWAIT ZMQ_NOBLOCK 24 | #endif 25 | 26 | #endif // !defined(__ZMQ_SHIM_H__) 27 | -------------------------------------------------------------------------------- /src/command_line.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * command_line.c 3 | * 4 | * parse the command line 5 | *--------------------------------------------------------------------------*/ 6 | #include 7 | 8 | #include "bstrlib.h" 9 | #include "dbg_syslog.h" 10 | 11 | static const char * optstring = "c:"; 12 | 13 | //---------------------------------------------------------------------------- 14 | // sets config_path from -c 15 | // returns 0 for success, -1 for failure 16 | int 17 | parse_command_line(int argc, char **argv, bstring *config_path) { 18 | //---------------------------------------------------------------------------- 19 | int opt; 20 | 21 | while ((opt = getopt(argc, argv, optstring)) != -1) { 22 | check(opt == 'c', "unexpectged opt '%d'", opt); 23 | check(optarg != NULL, "NULL arg"); 24 | *config_path = bfromcstr(optarg); 25 | check(*config_path != NULL, "bfromcstr"); 26 | } 27 | 28 | return 0; 29 | error: 30 | return -1; 31 | } 32 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * config.h 3 | * 4 | * configuration from .skeeterc 5 | *--------------------------------------------------------------------------*/ 6 | #if !defined(__config_h__) 7 | #define __config_h__ 8 | 9 | #include 10 | #include 11 | 12 | #include "bstrlib.h" 13 | 14 | static const size_t MAX_POSTGRESQL_OPTIONS = 20; 15 | 16 | struct Config { 17 | int zmq_thread_pool_size; 18 | const char * pub_socket_uri; 19 | int pub_socket_hwm; 20 | 21 | int epoll_timeout; 22 | time_t heartbeat_interval; 23 | 24 | time_t database_retry_interval; 25 | const char ** postgresql_keywords; 26 | const char ** postgresql_values; 27 | struct bstrList * channel_list; 28 | }; 29 | 30 | // load config from skeeterrc 31 | extern const struct Config * 32 | load_config(bstring config_path); 33 | 34 | // release resources used by config 35 | extern void 36 | clear_config(const struct Config * config); 37 | 38 | #endif // !defined(__config_h__) 39 | 40 | -------------------------------------------------------------------------------- /src/signal_handler.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * signal_handler.c 3 | * creatge and install a signal handler to set halt_signal to 1 on 4 | * SIGKILL or SIGTERM 5 | * 6 | *--------------------------------------------------------------------------*/ 7 | 8 | #include 9 | #include 10 | 11 | #include "dbg_syslog.h" 12 | 13 | bool halt_signal = false; 14 | 15 | static void 16 | signal_handler(int signal) { 17 | (void) signal; // unused 18 | debug("signal %d", signal); 19 | halt_signal = true; 20 | } 21 | 22 | // install the same signal handler for SIGINT and SIGTERM 23 | int 24 | install_signal_handler() { 25 | 26 | struct sigaction action; 27 | action.sa_handler = signal_handler; 28 | sigemptyset(&action.sa_mask); 29 | action.sa_flags = 0; 30 | 31 | check(sigaction(SIGINT, &action, NULL) == 0, "sigaction, SIGINT"); 32 | check(sigaction(SIGTERM, &action, NULL) == 0, "sigaction, SIGTERM"); 33 | 34 | return 0; 35 | 36 | error: 37 | 38 | return -1; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/state.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * state.h 3 | * 4 | * State struct and supporting functions 5 | *--------------------------------------------------------------------------*/ 6 | #if !defined(__STATE_H__) 7 | #define __STATE_H__ 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include "config.h" 14 | 15 | struct State { 16 | int heartbeat_timer_fd; 17 | struct epoll_event heartbeat_timer_event; 18 | 19 | int restart_timer_fd; 20 | struct epoll_event restart_timer_event; 21 | 22 | PGconn * postgres_connection; 23 | time_t postgres_connect_time; 24 | struct epoll_event postgres_event; 25 | 26 | int epoll_fd; 27 | 28 | void * zmq_pub_socket; 29 | 30 | uint64_t heartbeat_count; 31 | 32 | // parallel array to config.channel_list 33 | uint64_t * channel_counts; 34 | }; 35 | 36 | extern struct State * 37 | create_state(const struct Config * config); 38 | 39 | extern void 40 | clear_state(struct State * state); 41 | 42 | #endif // !defined(__STATE_H__) 43 | -------------------------------------------------------------------------------- /src/display_strings.c: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------- 2 | // display_strings.c 3 | // 4 | // string values of constants used for debugging and information display 5 | //---------------------------------------------------------------------------- 6 | 7 | // strings for postgres ConnStatusType 8 | const char * CONN_STATUS[] = { 9 | "CONNECTION_OK", 10 | "CONNECTION_BAD", 11 | "CONNECTION_STARTED", 12 | "CONNECTION_MADE", 13 | "CONNECTION_AWAITING_RESPONSE", 14 | "CONNECTION_AUTH_OK", 15 | "CONNECTION_SETENV", 16 | "CONNECTION_SSL_STARTUP", 17 | "CONNECTION_NEEDED" 18 | }; 19 | 20 | const char * POLLING_STATUS[] = { 21 | "PGRES_POLLING_FAILED", 22 | "PGRES_POLLING_READING", 23 | "PGRES_POLLING_WRITING", 24 | "PGRES_POLLING_OK", 25 | "PGRES_POLLING_ACTIVE" 26 | }; 27 | 28 | 29 | const char * EXEC_STATUS[] = { 30 | "PGRES_EMPTY_QUERY", 31 | "PGRES_COMMAND_OK", 32 | "PGRES_TUPLES_OK", 33 | "PGRES_COPY_OUT", 34 | "PGRES_COPY_IN", 35 | "PGRES_BAD_RESPONSE", 36 | "PGRES_NONFATAL_ERROR", 37 | "PGRES_FATAL_ERROR", 38 | "PGRES_COPY_BOTH" 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /test/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | config.py 4 | 5 | parse skeeterrc so the test programs use the same parameters as skeeter 6 | """ 7 | import logging 8 | import os.path 9 | import sys 10 | 11 | class ConfigError(Exception): 12 | pass 13 | 14 | _default_path = os.path.expanduser("~/.skeeterrc") 15 | _postgresql_tag = "postgresql-" 16 | 17 | def load_config(): 18 | """ 19 | load the config file, either from a path specified on the comandline 20 | or from the default location 21 | """ 22 | log = logging.getLogger("load_config") 23 | if len(sys.argv) == 1: 24 | config_path = _default_path 25 | elif len(sys.argv) == 2: 26 | config_path = sys.argv[1] 27 | elif len(sys.argv) == 3 and sys.argv[1] == "-c": 28 | config_path = sys.argv[2] 29 | else: 30 | raise ConfigError("unparsable commandline {0}".format(sys.argv)) 31 | 32 | config = {"database-credentials" : dict()} 33 | 34 | log.info("reading config from {0}".format(config_path)) 35 | for line in open(config_path): 36 | line = line.strip() 37 | if len(line) == 0 or line.startswith("#"): 38 | continue 39 | key, value = line.split("=") 40 | key = key.strip() 41 | value = value.strip() 42 | 43 | if key.startswith(_postgresql_tag): 44 | key = key[len(_postgresql_tag):] 45 | if key == "dbname": 46 | key = "database" 47 | config["database-credentials"][key] = value 48 | elif key == "channels": 49 | config[key] = [c.strip() for c in value.split(",")] 50 | else: 51 | config[key] = value 52 | 53 | return config 54 | 55 | -------------------------------------------------------------------------------- /src/message.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * message.c 3 | * 4 | * publish a zeromq message 5 | *--------------------------------------------------------------------------*/ 6 | 7 | #include 8 | 9 | #include "bstrlib.h" 10 | #include "dbg_syslog.h" 11 | #include "zmq_shim.h" 12 | 13 | //--------------------------------------------------------------------------- 14 | // send a (possibly multipart) message over the pub socket 15 | // all strings are copied to zmq message structures 16 | // it is the responsibility of the caller to clean up message_list 17 | // return 0 for success, -1 for failure 18 | int 19 | publish_message(const struct bstrList * message_list, void * zmq_pub_socket) { 20 | //--------------------------------------------------------------------------- 21 | int i; 22 | zmq_msg_t message; 23 | size_t message_size; 24 | const char * cstr; 25 | int flag; 26 | int result; 27 | 28 | for (i=0; i < message_list->qty; i++) { 29 | message_size = blength(message_list->entry[i]); 30 | check(zmq_msg_init_size(&message, message_size) == 0, 31 | "zmq_init_size %d", 32 | (int) message_size); 33 | cstr = bstr2cstr(message_list->entry[i], '?'); 34 | check(cstr != NULL, "bstr2cstr"); 35 | memcpy(zmq_msg_data(&message), cstr, message_size); 36 | check(bcstrfree((char *)cstr) == BSTR_OK, "bcstrfree"); 37 | flag = (i == (message_list->qty)-1) ? 0 : ZMQ_SNDMORE; 38 | result = zmq_msg_send(&message, zmq_pub_socket, flag); 39 | check(result != -1, "zmq_send channel_message"); 40 | check(zmq_msg_close(&message) == 0, "close messge"); 41 | 42 | } 43 | return 0; 44 | 45 | error: 46 | return -1; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /skeeterrc: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # skeeterrc 3 | # 4 | # config file for skeeter 5 | ############################################################################### 6 | 7 | 8 | # zeromq parameters 9 | 10 | zmq_thread_pool_size=3 11 | 12 | # the uri on which we publish database nofitications 13 | pub_socket_uri=tcp://127.0.0.1:6666 14 | 15 | # the high water mark: max number of messages to queue 16 | # if not specified, the defualt is 'unlimited' 17 | pub_socket_hwm=5 18 | 19 | # timing parameters 20 | 21 | # timeout for epoll() (in seconds) 22 | epoll_timeout=10 23 | 24 | # frequency (in seconds) that a heartbeat message is published 25 | heartbeat_interval=10 26 | 27 | # interval (in seconds) that we try to re-connect to the database 28 | database_retry_interval=30 29 | 30 | ## ------------------------------------------------------------------------- 31 | ## postgresql keyword options 32 | ## prefix with 'postgresql-' 33 | ## for example 'postgresql-dbname 34 | ## ------------------------------------------------------------------------- 35 | #postgresql-host 36 | #postgresql-hostaddr 37 | #postgresql-port 38 | postgresql-dbname=postgres 39 | #postgresql-user 40 | #postgresql-password 41 | #postgresql-connect_timeout 42 | #postgresql-options 43 | #postgresql-application_name 44 | #postgresql-fallback_application_name 45 | #postgresql-keepalives 46 | #postgresql-keepalives_idle 47 | #postgresql-keepalives_interval 48 | #postgresql-keepalives_count 49 | #postgresql-sslmode 50 | #postgresql-requiressl 51 | #postgresql-sslcert 52 | #postgresql-sslkey 53 | #postgresql-sslrootcert 54 | #postgresql-sslcrl 55 | #postgresql-krbsrvname 56 | #postgresql-gsslib 57 | #postgresql-service 58 | ## -------------------------------------------------------------------------- 59 | 60 | # channels to watch for 61 | # comma separated list of strings 62 | channels=channel1,channel2,channel3 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/state.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * state.c 3 | * 4 | * State struct and supporting functions 5 | *--------------------------------------------------------------------------*/ 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "dbg_syslog.h" 13 | #include "state.h" 14 | 15 | //---------------------------------------------------------------------------- 16 | struct State * 17 | create_state(const struct Config * config) { 18 | //---------------------------------------------------------------------------- 19 | struct State * state = malloc(sizeof(struct State)); 20 | check_mem(state); 21 | bzero(state, sizeof(struct State)); 22 | 23 | state->heartbeat_timer_fd = -1; 24 | 25 | state->restart_timer_fd = -1; 26 | 27 | state->postgres_connection = NULL; 28 | state->postgres_connect_time = 0; 29 | 30 | state->epoll_fd = -1; 31 | 32 | state->zmq_pub_socket = NULL; 33 | 34 | state->heartbeat_count = 0; 35 | 36 | state->channel_counts = calloc(config->channel_list->qty, sizeof(uint64_t)); 37 | check_mem(state->channel_counts); 38 | 39 | return state; 40 | 41 | error: 42 | 43 | return NULL; 44 | } 45 | 46 | 47 | //---------------------------------------------------------------------------- 48 | // release resources used by state 49 | void 50 | clear_state(struct State * state) { 51 | //---------------------------------------------------------------------------- 52 | if (state->heartbeat_timer_fd != -1) close(state->heartbeat_timer_fd); 53 | if (state->restart_timer_fd != -1) close(state->restart_timer_fd); 54 | if (state->postgres_connection != NULL) { 55 | PQfinish(state->postgres_connection); 56 | } 57 | if (state->epoll_fd != -1) close(state->epoll_fd); 58 | if (state->zmq_pub_socket != NULL) zmq_close(state->zmq_pub_socket); 59 | free(state->channel_counts); 60 | free(state); 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/dbg.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * Zed Shaw's cosmic macros 3 | * copied from Learn C the Hard Way 4 | * http://c.learncodethehardway.org/book/ 5 | *--------------------------------------------------------------------------*/ 6 | #if !defined(__dbg_h__) 7 | #define __dbg_h__ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #if defined(NDEBUG) 14 | #define debug(M, ...) 15 | #else 16 | #define debug(M, ...) fprintf(stderr, \ 17 | "DEBUG %s:%d: " M "\n", \ 18 | __FILE__, \ 19 | __LINE__, \ 20 | ##__VA_ARGS__) 21 | #endif // defined(NDEBUG) 22 | 23 | #define clean_errno() (errno == 0 ? "None" : strerror(errno)) 24 | 25 | #define log_err(M, ...) fprintf(stderr, \ 26 | "[ERROR] %s:%d: errno: %s " M "\n", \ 27 | __FILE__, \ 28 | __LINE__, \ 29 | clean_errno(), \ 30 | ##__VA_ARGS__) 31 | 32 | #define log_warn(M, ...) fprintf(stderr, \ 33 | "[WARN] %s:%d: errno: %s " M "\n", \ 34 | __FILE__, \ 35 | __LINE__, \ 36 | clean_errno(), \ 37 | ##__VA_ARGS__) 38 | 39 | #define log_info(M, ...) fprintf(stderr, \ 40 | "[INFO] %s:%d: " M "\n", \ 41 | __FILE__, \ 42 | __LINE__, \ 43 | ##__VA_ARGS__) 44 | 45 | #define check(A, M, ...) if (!(A)) { log_err(M, ##__VA_ARGS__); \ 46 | errno = 0; \ 47 | goto error; } 48 | 49 | #define sentinel(M, ...) { log_err(M, ##__VA_ARGS__); \ 50 | errno = 0; \ 51 | goto error; } 52 | 53 | #define check_mem(A) check((A), "out of memory.") 54 | 55 | #define check_debug(A, M, ...) if (!(A)) { debug(M, ##__VA_ARGS__); \ 56 | errno = 0; \ 57 | goto error; } 58 | #endif // !defined(__dbg_h__) 59 | -------------------------------------------------------------------------------- /test/test_skeeter_notifyer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | test_skeeter_notifier.py 4 | 5 | This is a Python script to test the skeeter program. 6 | 7 | It will call pg_notify in the database so the subscriber can report 8 | """ 9 | import logging 10 | import random 11 | import signal 12 | import sys 13 | from threading import Event 14 | 15 | import psycopg2 16 | 17 | from test.config import load_config 18 | 19 | _low_delay = 0.0 20 | _high_delay = 3.0 21 | _low_data_size = 0 22 | _high_data_size = 8000 23 | 24 | def _initialize_logging(): 25 | handler = logging.StreamHandler() 26 | formatter = logging.Formatter( 27 | '%(asctime)s %(levelname)-8s %(name)-20s: %(message)s') 28 | handler.setFormatter(formatter) 29 | logging.root.addHandler(handler) 30 | logging.root.setLevel(logging.DEBUG) 31 | 32 | def _create_signal_handler(halt_event): 33 | def cb_handler(*_): 34 | halt_event.set() 35 | return cb_handler 36 | 37 | def _set_signal_handler(halt_event): 38 | """ 39 | set a signal handler to set halt_event when SIGTERM is raised 40 | """ 41 | signal.signal(signal.SIGTERM, _create_signal_handler(halt_event)) 42 | 43 | def main(): 44 | """ 45 | main entry point 46 | 47 | returns 0 for success 48 | 1 for failure 49 | """ 50 | _initialize_logging() 51 | log = logging.getLogger("main") 52 | log.info("program starts") 53 | 54 | config = load_config() 55 | 56 | database_connection = psycopg2.connect(**config["database-credentials"]) 57 | 58 | return_value = 0 59 | 60 | halt_event = Event() 61 | _set_signal_handler(halt_event) 62 | while not halt_event.is_set(): 63 | channel = random.choice(config["channels"]) 64 | data_size = random.randint(_low_data_size, _high_data_size-1) 65 | data = 'a' * data_size 66 | log.info("notifying {0} with {1} bytes".format(channel, data_size)) 67 | try: 68 | cursor = database_connection.cursor() 69 | cursor.execute("select pg_notify(%s, %s);", [channel, data, ]) 70 | cursor.close() 71 | database_connection.commit() 72 | except KeyboardInterrupt: 73 | log.info("keyboard interrupt") 74 | halt_event.set() 75 | except Exception as instance: 76 | log.exception(str(instance)) 77 | return_value = 1 78 | halt_event.set() 79 | else: 80 | halt_event.wait(random.uniform(_low_delay, _high_delay)) 81 | 82 | log.info("program terminates with return_value {0}".format(return_value)) 83 | database_connection.close() 84 | 85 | return return_value 86 | 87 | if __name__ == "__main__": 88 | sys.exit(main()) 89 | 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Skeeter 2 | ======= 3 | 4 | Synopsis 5 | -------- 6 | 7 | This program is named after the ubiquitous reporter Rita Skeeter from the 8 | Harry Potter novels. Like Rita, this program will LISTEN (to postgres) and 9 | PUBlish (to zeromq). 10 | 11 | We at SpiderOak developed this to reduce the number of open database 12 | connectons. Instead of every program opening a database connection to 13 | listen for Postgresql notifications, they can subscribe to a channel 14 | on the PUB server. 15 | 16 | Our servers are currently running Ubuntu 10.4 LTS. Skeeter is mainstream code, 17 | so it should run on any reasonably current Linux. 18 | 19 | Usage 20 | ----- 21 | 22 | > `skeeter [-c=]` 23 | 24 | skeeter will look for the config file at `${HOME}/.skeeterrc` by default. 25 | 26 | Building 27 | -------- 28 | 29 | Development build: 30 | > `make dev` 31 | 32 | Production build 33 | > `make` 34 | 35 | * The development build is good for use with [valgrind](http://valgrind.org/) 36 | * The production build sets NDEBUG which, among other things, causes logging to 37 | syslog. 38 | 39 | Dependencies 40 | ------------ 41 | 42 | We use Postgresql 9.x. In addition to the server, you will need the development 43 | library. On Ubuntu: `sudo apt-get install libpq-dev` 44 | 45 | We use a simple ZeroMQ PUB server. You will need the ZeroMQ development 46 | library. On Ubuntu: `sudo apt-get install libzmq-dev`. 47 | 48 | Configuration 49 | ------------- 50 | 51 | Skeeter uses a config file to define parameters that are site specific, or 52 | tunable. By default the config file is located at `${HOME}/.skeeterrc` 53 | but you can override this from the command line. 54 | 55 | We have included a sample config file, `skeeterrc` with comments 56 | documentating the options. 57 | 58 | Testing/Example Code 59 | -------------------- 60 | 61 | We don't have any unit tests (sorry Zed). We do have a test framework 62 | consisting of two python programs: 63 | 64 | * `test_skeeter_notifyer.py` 65 | 66 | This program reads the skeeterrc config file and posts random 67 | notifications to the database. 68 | 69 | * `test_skeeter_subscriber.py` 70 | 71 | This program reads the skeeterrc config file and subscribes to the 72 | 0mq PUB socket. It logs reported events to stdout. 73 | 74 | Acknowledgements 75 | ---------------- 76 | 77 | This code is heavily influenced by Zed Shaw's ['Learn C the Hard Way'](http://c.learncodethehardway.org/book/) 78 | 79 | dbg.h is copied directly from LCTHW also, the use of [bstrings](http://bstring.sourceforge.net/) 80 | 81 | You should read the truly excellent [0mq Guide](http://zguide.zeromq.org/) 82 | 83 | Contact us 84 | ---------- 85 | 86 | This is an open source project from [SpiderOak](https://SpiderOak.com) 87 | 88 | send mail to Doug Fort dougfort@spideroak.com 89 | 90 | -------------------------------------------------------------------------------- /src/dbg_syslog.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * Zed Shaw's cosmic macros 3 | * copied from Learn C the Hard Way 4 | * http://c.learncodethehardway.org/book/ 5 | * with non debug logging going to syslog 6 | *--------------------------------------------------------------------------*/ 7 | #if !defined(__DBG_SYSLOG_H__) 8 | #define __DBG_SYSLOG_H__ 9 | 10 | #if defined(NDEBUG) 11 | #include 12 | #else 13 | #include 14 | #endif // NDEBUG 15 | 16 | #include 17 | #include 18 | 19 | #define clean_errno() (errno == 0 ? "None" : strerror(errno)) 20 | 21 | #if defined(NDEBUG) 22 | #define debug(M, ...) 23 | 24 | #define log_err(M, ...) syslog(LOG_ERR, \ 25 | "[ERROR] %s:%d: errno: %s " M "\n", \ 26 | __FILE__, \ 27 | __LINE__, \ 28 | clean_errno(), \ 29 | ##__VA_ARGS__) 30 | 31 | #define log_warn(M, ...) syslog(LOG_WARNING \ 32 | "[WARN] %s:%d: errno: %s " M "\n", \ 33 | __FILE__, \ 34 | __LINE__, \ 35 | clean_errno(), \ 36 | ##__VA_ARGS__) 37 | 38 | #define log_info(M, ...) syslog(LOG_INFO, \ 39 | "[INFO] %s:%d: " M "\n", \ 40 | __FILE__, \ 41 | __LINE__, \ 42 | ##__VA_ARGS__) 43 | 44 | #else 45 | #define debug(M, ...) fprintf(stderr, \ 46 | "DEBUG %s:%d: " M "\n", \ 47 | __FILE__, \ 48 | __LINE__, \ 49 | ##__VA_ARGS__) 50 | 51 | #define log_err(M, ...) fprintf(stderr, \ 52 | "[ERROR] %s:%d: errno: %s " M "\n", \ 53 | __FILE__, \ 54 | __LINE__, \ 55 | clean_errno(), \ 56 | ##__VA_ARGS__) 57 | 58 | #define log_warn(M, ...) fprintf(stderr, \ 59 | "[WARN] %s:%d: errno: %s " M "\n", \ 60 | __FILE__, \ 61 | __LINE__, \ 62 | clean_errno(), \ 63 | ##__VA_ARGS__) 64 | 65 | #define log_info(M, ...) fprintf(stderr, \ 66 | "[INFO] %s:%d: " M "\n", \ 67 | __FILE__, \ 68 | __LINE__, \ 69 | ##__VA_ARGS__) 70 | 71 | #endif // defined(NDEBUG) 72 | 73 | #define check(A, M, ...) if (!(A)) { log_err(M, ##__VA_ARGS__); \ 74 | errno = 0; \ 75 | goto error; } 76 | 77 | #define sentinel(M, ...) { log_err(M, ##__VA_ARGS__); \ 78 | errno = 0; \ 79 | goto error; } 80 | 81 | #define check_mem(A) check((A), "out of memory.") 82 | 83 | #define check_debug(A, M, ...) if (!(A)) { debug(M, ##__VA_ARGS__); \ 84 | errno = 0; \ 85 | goto error; } 86 | #endif // !defined(__DBG_SYSLOG_H__) 87 | -------------------------------------------------------------------------------- /test/test_skeeter_subscriber.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | test_skeeter_subscriber.py 4 | 5 | This is a Python script to test the skeeter program by subscribing to 6 | the PUB socket and reporting notifications. 7 | """ 8 | import logging 9 | import signal 10 | import sys 11 | from threading import Event 12 | import time 13 | 14 | import zmq 15 | 16 | from test.config import load_config 17 | 18 | class SequenceError(Exception): 19 | pass 20 | 21 | def _initialize_logging(): 22 | handler = logging.StreamHandler() 23 | formatter = logging.Formatter( 24 | '%(asctime)s %(levelname)-8s %(name)-20s: %(message)s') 25 | handler.setFormatter(formatter) 26 | logging.root.addHandler(handler) 27 | logging.root.setLevel(logging.DEBUG) 28 | 29 | def _create_signal_handler(halt_event): 30 | def cb_handler(*_): 31 | halt_event.set() 32 | return cb_handler 33 | 34 | def _set_signal_handler(halt_event): 35 | """ 36 | set a signal handler to set halt_event when SIGTERM is raised 37 | """ 38 | signal.signal(signal.SIGTERM, _create_signal_handler(halt_event)) 39 | 40 | def _process_one_event(expected_sequence, topic, meta, data): 41 | log = logging.getLogger("event") 42 | 43 | meta_dict = dict() 44 | for entry in meta.strip().split(";"): 45 | [key, value] = entry.split("=") 46 | meta_dict[key.strip()] = value.strip() 47 | 48 | # every event should have a sequence and a timestamp 49 | meta_dict["timestamp"] = time.ctime(int(meta_dict["timestamp"])) 50 | meta_dict["sequence"] = int(meta_dict["sequence"]) 51 | 52 | if topic == "heartbeat": 53 | connect_time = int(meta_dict["connected"]) 54 | if connect_time == 0: 55 | connect_str = "*not connected*" 56 | else: 57 | connect_str = time.ctime(connect_time) 58 | line = "{0:30} {1:20} {2:8} connected={3}".format( 59 | meta_dict["timestamp"], 60 | topic, 61 | meta_dict["sequence"], 62 | connect_str) 63 | else: 64 | line = "{0:30} {1:20} {2:8} data_bytes={3}".format( 65 | meta_dict["timestamp"], 66 | topic, 67 | meta_dict["sequence"], 68 | len(data)) 69 | 70 | log.info(line) 71 | 72 | if expected_sequence[topic] is None: 73 | expected_sequence[topic] = meta_dict["sequence"] + 1 74 | elif meta_dict["sequence"] == expected_sequence[topic]: 75 | expected_sequence[topic] += 1 76 | else: 77 | message = \ 78 | "{0} out of sequence expected {1} found {2}".format( 79 | topic, expected_sequence[topic], meta_dict["sequence"]) 80 | raise SequenceError(message) 81 | 82 | def main(): 83 | """ 84 | main entry point 85 | 86 | returns 0 for success 87 | 1 for failure 88 | """ 89 | _initialize_logging() 90 | log = logging.getLogger("main") 91 | log.info("program starts") 92 | 93 | zeromq_context = zmq.Context() 94 | 95 | config = load_config() 96 | 97 | sub_socket = zeromq_context.socket(zmq.SUB) 98 | 99 | hwm = config.get("hwm") 100 | if hwm is not None: 101 | log.info("setting sub_socket HWM to {0}".format(hwm)) 102 | sub_socket.setsockopt(zmq.HWM, hwm) 103 | 104 | expected_sequence = dict() 105 | 106 | log.info("subscribing to heartbeat") 107 | sub_socket.setsockopt(zmq.SUBSCRIBE, "heartbeat".encode("utf-8")) 108 | expected_sequence["heartbeat"] = None 109 | 110 | for channel in config["channels"]: 111 | log.info("subscribing to {0}".format(channel)) 112 | sub_socket.setsockopt(zmq.SUBSCRIBE, channel.encode("utf-8")) 113 | expected_sequence[channel] = None 114 | 115 | log.info("connecting sub_socket to {0}".format(config["pub_socket_uri"])) 116 | sub_socket.connect(config["pub_socket_uri"]) 117 | 118 | return_value = 0 119 | 120 | halt_event = Event() 121 | _set_signal_handler(halt_event) 122 | while not halt_event.is_set(): 123 | try: 124 | topic_bytes = sub_socket.recv() 125 | topic = topic_bytes.decode("utf-8") 126 | assert sub_socket.rcvmore 127 | meta_bytes = sub_socket.recv() 128 | meta = meta_bytes.decode("utf-8") 129 | if sub_socket.rcvmore: 130 | data_bytes = sub_socket.recv() 131 | data = data_bytes.decode("utf-8") 132 | else: 133 | data = "" 134 | except KeyboardInterrupt: 135 | log.info("keyboard interrupt") 136 | halt_event.set() 137 | except Exception as instance: 138 | log.exception(str(instance)) 139 | return_value = 1 140 | halt_event.set() 141 | else: 142 | _process_one_event(expected_sequence, topic, meta, data) 143 | 144 | log.info("program terminates with return_value {0}".format(return_value)) 145 | sub_socket.close() 146 | zeromq_context.term() 147 | 148 | return return_value 149 | 150 | if __name__ == "__main__": 151 | sys.exit(main()) 152 | 153 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * config.c 3 | * 4 | * configuration from skeeterrc 5 | *--------------------------------------------------------------------------*/ 6 | #include 7 | #include 8 | #include 9 | 10 | #include "bstrlib.h" 11 | #include "config.h" 12 | #include "dbg_syslog.h" 13 | 14 | int 15 | bstr2int(bstring bstr) { 16 | char * cstr; 17 | int value; 18 | 19 | cstr = bstr2cstr(bstr, '?'); 20 | value = atoi(cstr); 21 | bcstrfree(cstr); 22 | 23 | return value; 24 | } 25 | 26 | //---------------------------------------------------------------------------- 27 | // create a postgres keyword entry from a config line that begins wiht the 28 | // postgres prefix 29 | int 30 | postgres_entry(struct Config * config, 31 | bstring postgres_prefix, 32 | int count, 33 | struct bstrList * split_list) { 34 | //---------------------------------------------------------------------------- 35 | bstring keyword; 36 | 37 | keyword = bmidstr(split_list->entry[0], 38 | blength(postgres_prefix), 39 | blength(split_list->entry[0])); 40 | check(keyword != NULL, "bmidstr"); 41 | 42 | config->postgresql_keywords = realloc(config->postgresql_keywords, 43 | (count+1) * sizeof(char *)); 44 | check_mem(config->postgresql_keywords); 45 | config->postgresql_keywords[count-1] = bstr2cstr(keyword, '?'); 46 | config->postgresql_keywords[count] = NULL; 47 | 48 | check(bdestroy(keyword) == BSTR_OK, "keyword"); 49 | 50 | config->postgresql_values = realloc(config->postgresql_values, 51 | (count+1) * sizeof(char *)); 52 | check_mem(config->postgresql_values); 53 | config->postgresql_values[count-1] = bstr2cstr(split_list->entry[1], '?'); 54 | debug("value = '%s'", config->postgresql_values[count-1]); 55 | config->postgresql_values[count] = NULL; 56 | 57 | return 0; 58 | error: 59 | return -1; 60 | } 61 | 62 | int 63 | parse_channel_list(struct Config * config, bstring entry) { 64 | int i; 65 | 66 | config->channel_list = bsplit(entry, ','); 67 | check(config->channel_list != NULL, "bsplit"); 68 | for (i=0; i < config->channel_list->qty; i++) { 69 | check(btrimws(config->channel_list->entry[i]) == BSTR_OK, 70 | "btrimws"); 71 | } 72 | 73 | return 0; 74 | error: 75 | return -1; 76 | } 77 | 78 | int 79 | set_config_defaults(struct Config * config) { 80 | // set defaults 81 | config->zmq_thread_pool_size = 3; 82 | config->heartbeat_interval = 10; 83 | config->epoll_timeout = 1; 84 | config->pub_socket_uri = NULL; 85 | config->pub_socket_hwm = 5; 86 | 87 | config->postgresql_keywords = malloc(sizeof(char *)); 88 | check_mem(config->postgresql_keywords); 89 | config->postgresql_keywords[0] = NULL; 90 | 91 | config->postgresql_values = malloc(sizeof(char *)); 92 | check_mem(config->postgresql_values); 93 | config->postgresql_values[0] = NULL; 94 | 95 | config->channel_list = NULL; 96 | 97 | return 0; 98 | 99 | error: 100 | return -1; 101 | } 102 | 103 | //---------------------------------------------------------------------------- 104 | // load config from skeeterrc 105 | const struct Config * 106 | load_config(bstring config_path) { 107 | //---------------------------------------------------------------------------- 108 | struct Config * config = NULL; 109 | FILE * config_stream = NULL; 110 | struct bStream * config_bstream = NULL; 111 | const char * config_path_cstr = NULL; 112 | bstring line = bfromcstr("");; 113 | int read_result; 114 | struct bstrList * split_list; 115 | bstring postgres_prefix = bfromcstr("postgresql-"); 116 | int postgres_count = 0; 117 | 118 | config_path_cstr = bstr2cstr(config_path, '?'); 119 | check(config_path_cstr != NULL, "bstr2cstr"); 120 | 121 | config = malloc(sizeof(struct Config)); 122 | check_mem(config); 123 | bzero(config, sizeof(struct Config)); 124 | 125 | check(set_config_defaults(config) == 0, "set_config_defaults"); 126 | 127 | config_stream = fopen(config_path_cstr, "r"); 128 | check(config_stream != NULL, "fopen(%s)", config_path_cstr); 129 | config_bstream = bsopen((bNread) fread, config_stream); 130 | check(config_bstream != NULL, "bsopen"); 131 | 132 | for (;;) { 133 | read_result = bsreadln(line, config_bstream, '\n'); 134 | if (bseof(config_bstream)) break; 135 | check(read_result == BSTR_OK, "bsreadln"); 136 | 137 | // skip blank lines and comments 138 | switch (bchar(line, 0)) { 139 | case '\0': 140 | case ' ': 141 | case '\n': 142 | case '#': 143 | continue; 144 | } 145 | 146 | if (bstrchrp(line, '=', 0) == BSTR_ERR) { 147 | log_err("Invalid config line %s", bstr2cstr(line, '?')); 148 | continue; 149 | } 150 | 151 | split_list = bsplit(line, '='); 152 | check(split_list != NULL, "NULL split_list"); 153 | check(split_list->qty == 2, "split error %d", split_list->qty); 154 | check(btrimws(split_list->entry[0]) == BSTR_OK, "trim[0]") 155 | check(btrimws(split_list->entry[1]) == BSTR_OK, "trim[1]") 156 | 157 | if (biseqcstr(split_list->entry[0], "zmq_thread_pool_size")) { 158 | config->zmq_thread_pool_size = bstr2int(split_list->entry[1]); 159 | } else if (biseqcstr(split_list->entry[0], "pub_socket_uri")) { 160 | config->pub_socket_uri = bstr2cstr(split_list->entry[1], '?'); 161 | } else if (biseqcstr(split_list->entry[0], "pub_socket_hwm")) { 162 | config->pub_socket_hwm = bstr2int(split_list->entry[1]); 163 | } else if (biseqcstr(split_list->entry[0], "epoll_timeout")) { 164 | config->epoll_timeout = bstr2int(split_list->entry[1]); 165 | } else if (biseqcstr(split_list->entry[0], "heartbeat_interval")) { 166 | config->heartbeat_interval = bstr2int(split_list->entry[1]); 167 | } else if (biseqcstr(split_list->entry[0], "database_retry_interval")) { 168 | config->database_retry_interval = bstr2int(split_list->entry[1]); 169 | } else if (bstrncmp(split_list->entry[0], 170 | postgres_prefix, 171 | blength(postgres_prefix)) == 0) { 172 | postgres_count++; 173 | check(postgres_entry(config, 174 | postgres_prefix, 175 | postgres_count, 176 | split_list) == 0, 177 | "postgres_entry"); 178 | } else if (biseqcstr(split_list->entry[0], "channels")) { 179 | check(parse_channel_list(config, split_list->entry[1]) == 0, 180 | "parse_channel_list"); 181 | } else { 182 | log_err("unknown keyword '%s", bstr2cstr(split_list->entry[0], '?')); 183 | } 184 | 185 | check(bstrListDestroy(split_list) == BSTR_OK, "bstrListDestroy"); 186 | } 187 | 188 | check(bdestroy(line) == BSTR_OK, "bdestroy(line)"); 189 | check(bdestroy(postgres_prefix) == BSTR_OK, "bdestroy(postgres_prefix"); 190 | check(bsclose(config_bstream) != NULL, "bsclose"); 191 | check(fclose(config_stream) == 0, "fclose"); 192 | check(bcstrfree((char *)config_path_cstr) == BSTR_OK, "bcstrfree"); 193 | 194 | return config; 195 | 196 | error: 197 | if (config_bstream != NULL) bsclose(config_bstream); 198 | if (config_stream != NULL) fclose(config_stream); 199 | if (config_path_cstr != NULL) bcstrfree((char *)config_path_cstr); 200 | 201 | return NULL; 202 | } 203 | 204 | //---------------------------------------------------------------------------- 205 | // release resources used by config 206 | // we do this mostly to make it easier to read valgrind output 207 | void 208 | clear_config(const struct Config * config) { 209 | //---------------------------------------------------------------------------- 210 | int i; 211 | 212 | bcstrfree((char *) config->pub_socket_uri); 213 | if (config->channel_list != NULL) { 214 | bstrListDestroy(config->channel_list); 215 | } 216 | for (i=0; ;i++) { 217 | if (config->postgresql_keywords[i] == NULL) break; 218 | bcstrfree((char *) config->postgresql_keywords[i]); 219 | bcstrfree((char *) config->postgresql_values[i]); 220 | } 221 | free((void *) config->postgresql_keywords); 222 | free((void *) config->postgresql_values); 223 | 224 | free((void *) config); 225 | } 226 | 227 | -------------------------------------------------------------------------------- /src/bstrlib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the bstring string library. This code was 3 | * written by Paul Hsieh in 2002-2010, and is covered by either the 3-clause 4 | * BSD open source license or GPL v2.0. Refer to the accompanying documentation 5 | * for details on usage and license. 6 | */ 7 | 8 | /* 9 | * bstrlib.h 10 | * 11 | * This file is the header file for the core module for implementing the 12 | * bstring functions. 13 | */ 14 | 15 | #ifndef BSTRLIB_INCLUDE 16 | #define BSTRLIB_INCLUDE 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #if !defined (BSTRLIB_VSNP_OK) && !defined (BSTRLIB_NOVSNP) 28 | # if defined (__TURBOC__) && !defined (__BORLANDC__) 29 | # define BSTRLIB_NOVSNP 30 | # endif 31 | #endif 32 | 33 | #define BSTR_ERR (-1) 34 | #define BSTR_OK (0) 35 | #define BSTR_BS_BUFF_LENGTH_GET (0) 36 | 37 | typedef struct tagbstring * bstring; 38 | typedef const struct tagbstring * const_bstring; 39 | 40 | /* Copy functions */ 41 | #define cstr2bstr bfromcstr 42 | extern bstring bfromcstr (const char * str); 43 | extern bstring bfromcstralloc (int mlen, const char * str); 44 | extern bstring blk2bstr (const void * blk, int len); 45 | extern char * bstr2cstr (const_bstring s, char z); 46 | extern int bcstrfree (char * s); 47 | extern bstring bstrcpy (const_bstring b1); 48 | extern int bassign (bstring a, const_bstring b); 49 | extern int bassignmidstr (bstring a, const_bstring b, int left, int len); 50 | extern int bassigncstr (bstring a, const char * str); 51 | extern int bassignblk (bstring a, const void * s, int len); 52 | 53 | /* Destroy function */ 54 | extern int bdestroy (bstring b); 55 | 56 | /* Space allocation hinting functions */ 57 | extern int balloc (bstring s, int len); 58 | extern int ballocmin (bstring b, int len); 59 | 60 | /* Substring extraction */ 61 | extern bstring bmidstr (const_bstring b, int left, int len); 62 | 63 | /* Various standard manipulations */ 64 | extern int bconcat (bstring b0, const_bstring b1); 65 | extern int bconchar (bstring b0, char c); 66 | extern int bcatcstr (bstring b, const char * s); 67 | extern int bcatblk (bstring b, const void * s, int len); 68 | extern int binsert (bstring s1, int pos, const_bstring s2, unsigned char fill); 69 | extern int binsertch (bstring s1, int pos, int len, unsigned char fill); 70 | extern int breplace (bstring b1, int pos, int len, const_bstring b2, unsigned char fill); 71 | extern int bdelete (bstring s1, int pos, int len); 72 | extern int bsetstr (bstring b0, int pos, const_bstring b1, unsigned char fill); 73 | extern int btrunc (bstring b, int n); 74 | 75 | /* Scan/search functions */ 76 | extern int bstricmp (const_bstring b0, const_bstring b1); 77 | extern int bstrnicmp (const_bstring b0, const_bstring b1, int n); 78 | extern int biseqcaseless (const_bstring b0, const_bstring b1); 79 | extern int bisstemeqcaselessblk (const_bstring b0, const void * blk, int len); 80 | extern int biseq (const_bstring b0, const_bstring b1); 81 | extern int bisstemeqblk (const_bstring b0, const void * blk, int len); 82 | extern int biseqcstr (const_bstring b, const char * s); 83 | extern int biseqcstrcaseless (const_bstring b, const char * s); 84 | extern int bstrcmp (const_bstring b0, const_bstring b1); 85 | extern int bstrncmp (const_bstring b0, const_bstring b1, int n); 86 | extern int binstr (const_bstring s1, int pos, const_bstring s2); 87 | extern int binstrr (const_bstring s1, int pos, const_bstring s2); 88 | extern int binstrcaseless (const_bstring s1, int pos, const_bstring s2); 89 | extern int binstrrcaseless (const_bstring s1, int pos, const_bstring s2); 90 | extern int bstrchrp (const_bstring b, int c, int pos); 91 | extern int bstrrchrp (const_bstring b, int c, int pos); 92 | #define bstrchr(b,c) bstrchrp ((b), (c), 0) 93 | #define bstrrchr(b,c) bstrrchrp ((b), (c), blength(b)-1) 94 | extern int binchr (const_bstring b0, int pos, const_bstring b1); 95 | extern int binchrr (const_bstring b0, int pos, const_bstring b1); 96 | extern int bninchr (const_bstring b0, int pos, const_bstring b1); 97 | extern int bninchrr (const_bstring b0, int pos, const_bstring b1); 98 | extern int bfindreplace (bstring b, const_bstring find, const_bstring repl, int pos); 99 | extern int bfindreplacecaseless (bstring b, const_bstring find, const_bstring repl, int pos); 100 | 101 | /* List of string container functions */ 102 | struct bstrList { 103 | int qty, mlen; 104 | bstring * entry; 105 | }; 106 | extern struct bstrList * bstrListCreate (void); 107 | extern int bstrListDestroy (struct bstrList * sl); 108 | extern int bstrListAlloc (struct bstrList * sl, int msz); 109 | extern int bstrListAllocMin (struct bstrList * sl, int msz); 110 | 111 | /* String split and join functions */ 112 | extern struct bstrList * bsplit (const_bstring str, unsigned char splitChar); 113 | extern struct bstrList * bsplits (const_bstring str, const_bstring splitStr); 114 | extern struct bstrList * bsplitstr (const_bstring str, const_bstring splitStr); 115 | extern bstring bjoin (const struct bstrList * bl, const_bstring sep); 116 | extern int bsplitcb (const_bstring str, unsigned char splitChar, int pos, 117 | int (* cb) (void * parm, int ofs, int len), void * parm); 118 | extern int bsplitscb (const_bstring str, const_bstring splitStr, int pos, 119 | int (* cb) (void * parm, int ofs, int len), void * parm); 120 | extern int bsplitstrcb (const_bstring str, const_bstring splitStr, int pos, 121 | int (* cb) (void * parm, int ofs, int len), void * parm); 122 | 123 | /* Miscellaneous functions */ 124 | extern int bpattern (bstring b, int len); 125 | extern int btoupper (bstring b); 126 | extern int btolower (bstring b); 127 | extern int bltrimws (bstring b); 128 | extern int brtrimws (bstring b); 129 | extern int btrimws (bstring b); 130 | 131 | /* <*>printf format functions */ 132 | #if !defined (BSTRLIB_NOVSNP) 133 | extern bstring bformat (const char * fmt, ...); 134 | extern int bformata (bstring b, const char * fmt, ...); 135 | extern int bassignformat (bstring b, const char * fmt, ...); 136 | extern int bvcformata (bstring b, int count, const char * fmt, va_list arglist); 137 | 138 | #define bvformata(ret, b, fmt, lastarg) { \ 139 | bstring bstrtmp_b = (b); \ 140 | const char * bstrtmp_fmt = (fmt); \ 141 | int bstrtmp_r = BSTR_ERR, bstrtmp_sz = 16; \ 142 | for (;;) { \ 143 | va_list bstrtmp_arglist; \ 144 | va_start (bstrtmp_arglist, lastarg); \ 145 | bstrtmp_r = bvcformata (bstrtmp_b, bstrtmp_sz, bstrtmp_fmt, bstrtmp_arglist); \ 146 | va_end (bstrtmp_arglist); \ 147 | if (bstrtmp_r >= 0) { /* Everything went ok */ \ 148 | bstrtmp_r = BSTR_OK; \ 149 | break; \ 150 | } else if (-bstrtmp_r <= bstrtmp_sz) { /* A real error? */ \ 151 | bstrtmp_r = BSTR_ERR; \ 152 | break; \ 153 | } \ 154 | bstrtmp_sz = -bstrtmp_r; /* Doubled or target size */ \ 155 | } \ 156 | ret = bstrtmp_r; \ 157 | } 158 | 159 | #endif 160 | 161 | typedef int (*bNgetc) (void *parm); 162 | typedef size_t (* bNread) (void *buff, size_t elsize, size_t nelem, void *parm); 163 | 164 | /* Input functions */ 165 | extern bstring bgets (bNgetc getcPtr, void * parm, char terminator); 166 | extern bstring bread (bNread readPtr, void * parm); 167 | extern int bgetsa (bstring b, bNgetc getcPtr, void * parm, char terminator); 168 | extern int bassigngets (bstring b, bNgetc getcPtr, void * parm, char terminator); 169 | extern int breada (bstring b, bNread readPtr, void * parm); 170 | 171 | /* Stream functions */ 172 | extern struct bStream * bsopen (bNread readPtr, void * parm); 173 | extern void * bsclose (struct bStream * s); 174 | extern int bsbufflength (struct bStream * s, int sz); 175 | extern int bsreadln (bstring b, struct bStream * s, char terminator); 176 | extern int bsreadlns (bstring r, struct bStream * s, const_bstring term); 177 | extern int bsread (bstring b, struct bStream * s, int n); 178 | extern int bsreadlna (bstring b, struct bStream * s, char terminator); 179 | extern int bsreadlnsa (bstring r, struct bStream * s, const_bstring term); 180 | extern int bsreada (bstring b, struct bStream * s, int n); 181 | extern int bsunread (struct bStream * s, const_bstring b); 182 | extern int bspeek (bstring r, const struct bStream * s); 183 | extern int bssplitscb (struct bStream * s, const_bstring splitStr, 184 | int (* cb) (void * parm, int ofs, const_bstring entry), void * parm); 185 | extern int bssplitstrcb (struct bStream * s, const_bstring splitStr, 186 | int (* cb) (void * parm, int ofs, const_bstring entry), void * parm); 187 | extern int bseof (const struct bStream * s); 188 | 189 | struct tagbstring { 190 | int mlen; 191 | int slen; 192 | unsigned char * data; 193 | }; 194 | 195 | /* Accessor macros */ 196 | #define blengthe(b, e) (((b) == (void *)0 || (b)->slen < 0) ? (int)(e) : ((b)->slen)) 197 | #define blength(b) (blengthe ((b), 0)) 198 | #define bdataofse(b, o, e) (((b) == (void *)0 || (b)->data == (void*)0) ? (char *)(e) : ((char *)(b)->data) + (o)) 199 | #define bdataofs(b, o) (bdataofse ((b), (o), (void *)0)) 200 | #define bdatae(b, e) (bdataofse (b, 0, e)) 201 | #define bdata(b) (bdataofs (b, 0)) 202 | #define bchare(b, p, e) ((((unsigned)(p)) < (unsigned)blength(b)) ? ((b)->data[(p)]) : (e)) 203 | #define bchar(b, p) bchare ((b), (p), '\0') 204 | 205 | /* Static constant string initialization macro */ 206 | #define bsStaticMlen(q,m) {(m), (int) sizeof(q)-1, (unsigned char *) ("" q "")} 207 | #if defined(_MSC_VER) 208 | /* There are many versions of MSVC which emit __LINE__ as a non-constant. */ 209 | # define bsStatic(q) bsStaticMlen(q,-32) 210 | #endif 211 | #ifndef bsStatic 212 | # define bsStatic(q) bsStaticMlen(q,-__LINE__) 213 | #endif 214 | 215 | /* Static constant block parameter pair */ 216 | #define bsStaticBlkParms(q) ((void *)("" q "")), ((int) sizeof(q)-1) 217 | 218 | /* Reference building macros */ 219 | #define cstr2tbstr btfromcstr 220 | #define btfromcstr(t,s) { \ 221 | (t).data = (unsigned char *) (s); \ 222 | (t).slen = ((t).data) ? ((int) (strlen) ((char *)(t).data)) : 0; \ 223 | (t).mlen = -1; \ 224 | } 225 | #define blk2tbstr(t,s,l) { \ 226 | (t).data = (unsigned char *) (s); \ 227 | (t).slen = l; \ 228 | (t).mlen = -1; \ 229 | } 230 | #define btfromblk(t,s,l) blk2tbstr(t,s,l) 231 | #define bmid2tbstr(t,b,p,l) { \ 232 | const_bstring bstrtmp_s = (b); \ 233 | if (bstrtmp_s && bstrtmp_s->data && bstrtmp_s->slen >= 0) { \ 234 | int bstrtmp_left = (p); \ 235 | int bstrtmp_len = (l); \ 236 | if (bstrtmp_left < 0) { \ 237 | bstrtmp_len += bstrtmp_left; \ 238 | bstrtmp_left = 0; \ 239 | } \ 240 | if (bstrtmp_len > bstrtmp_s->slen - bstrtmp_left) \ 241 | bstrtmp_len = bstrtmp_s->slen - bstrtmp_left; \ 242 | if (bstrtmp_len <= 0) { \ 243 | (t).data = (unsigned char *)""; \ 244 | (t).slen = 0; \ 245 | } else { \ 246 | (t).data = bstrtmp_s->data + bstrtmp_left; \ 247 | (t).slen = bstrtmp_len; \ 248 | } \ 249 | } else { \ 250 | (t).data = (unsigned char *)""; \ 251 | (t).slen = 0; \ 252 | } \ 253 | (t).mlen = -__LINE__; \ 254 | } 255 | #define btfromblkltrimws(t,s,l) { \ 256 | int bstrtmp_idx = 0, bstrtmp_len = (l); \ 257 | unsigned char * bstrtmp_s = (s); \ 258 | if (bstrtmp_s && bstrtmp_len >= 0) { \ 259 | for (; bstrtmp_idx < bstrtmp_len; bstrtmp_idx++) { \ 260 | if (!isspace (bstrtmp_s[bstrtmp_idx])) break; \ 261 | } \ 262 | } \ 263 | (t).data = bstrtmp_s + bstrtmp_idx; \ 264 | (t).slen = bstrtmp_len - bstrtmp_idx; \ 265 | (t).mlen = -__LINE__; \ 266 | } 267 | #define btfromblkrtrimws(t,s,l) { \ 268 | int bstrtmp_len = (l) - 1; \ 269 | unsigned char * bstrtmp_s = (s); \ 270 | if (bstrtmp_s && bstrtmp_len >= 0) { \ 271 | for (; bstrtmp_len >= 0; bstrtmp_len--) { \ 272 | if (!isspace (bstrtmp_s[bstrtmp_len])) break; \ 273 | } \ 274 | } \ 275 | (t).data = bstrtmp_s; \ 276 | (t).slen = bstrtmp_len + 1; \ 277 | (t).mlen = -__LINE__; \ 278 | } 279 | #define btfromblktrimws(t,s,l) { \ 280 | int bstrtmp_idx = 0, bstrtmp_len = (l) - 1; \ 281 | unsigned char * bstrtmp_s = (s); \ 282 | if (bstrtmp_s && bstrtmp_len >= 0) { \ 283 | for (; bstrtmp_idx <= bstrtmp_len; bstrtmp_idx++) { \ 284 | if (!isspace (bstrtmp_s[bstrtmp_idx])) break; \ 285 | } \ 286 | for (; bstrtmp_len >= bstrtmp_idx; bstrtmp_len--) { \ 287 | if (!isspace (bstrtmp_s[bstrtmp_len])) break; \ 288 | } \ 289 | } \ 290 | (t).data = bstrtmp_s + bstrtmp_idx; \ 291 | (t).slen = bstrtmp_len + 1 - bstrtmp_idx; \ 292 | (t).mlen = -__LINE__; \ 293 | } 294 | 295 | /* Write protection macros */ 296 | #define bwriteprotect(t) { if ((t).mlen >= 0) (t).mlen = -1; } 297 | #define bwriteallow(t) { if ((t).mlen == -1) (t).mlen = (t).slen + ((t).slen == 0); } 298 | #define biswriteprotected(t) ((t).mlen <= 0) 299 | 300 | #ifdef __cplusplus 301 | } 302 | #endif 303 | 304 | #endif 305 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------- 2 | * main.c 3 | * 4 | * 5 | *--------------------------------------------------------------------------*/ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include "bstrlib.h" 22 | #include "command_line.h" 23 | #include "config.h" 24 | #include "dbg_syslog.h" 25 | #include "display_strings.h" 26 | #include "message.h" 27 | #include "signal_handler.h" 28 | #include "state.h" 29 | #include "zmq_shim.h" 30 | 31 | enum EPOLL_ACTION { 32 | EPOLL_READ, 33 | EPOLL_WRITE 34 | }; 35 | 36 | typedef enum CALLBACK_RESULT { 37 | CALLBACK_OK, 38 | CALLBACK_DATABASE_ERROR, 39 | CALLBACK_ERROR 40 | } CALLBACK_RESULT_TYPE; 41 | 42 | // The most epoll events that can be active 43 | // the restart_event and the postgres_event cannot be active at the same time 44 | static const int MAX_EPOLL_EVENTS = 2; 45 | 46 | typedef CALLBACK_RESULT_TYPE (* epoll_callback)(const struct Config * config, 47 | struct State * state); 48 | const char * PROGRAM_NAME = "skeeter"; 49 | 50 | // forward reference for callbacks 51 | int 52 | start_postgres_connection(const struct Config * config, struct State * state); 53 | 54 | //--------------------------------------------------------------------------- 55 | // compute the default path to the config file $HOME/.skeeterrc 56 | // return 0 for success, -1 for failure 57 | static int 58 | compute_default_config_path(bstring * config_path) { 59 | //--------------------------------------------------------------------------- 60 | char * home_dir = NULL; 61 | 62 | home_dir = getenv("HOME"); 63 | check(home_dir != NULL, "getenv"); 64 | *config_path = bfromcstr(home_dir); 65 | check(*config_path != NULL, "bfromcstr"); 66 | check(bcatcstr(*config_path, "/.skeeterrc") == BSTR_OK, "bcatstr home_dir"); 67 | 68 | return 0; 69 | 70 | error: 71 | return -1; 72 | } 73 | 74 | //--------------------------------------------------------------------------- 75 | // utility function for setting up epoll for postgres 76 | // returns 0 on success, -1 on error 77 | static int 78 | set_epoll_ctl_for_postgres(enum EPOLL_ACTION action, 79 | epoll_callback callback, 80 | struct State * state) { 81 | //--------------------------------------------------------------------------- 82 | int events = \ 83 | action == EPOLL_READ ? EPOLLIN | EPOLLERR : EPOLLOUT | EPOLLERR; 84 | int op = state->postgres_event.events == 0 ? EPOLL_CTL_ADD : EPOLL_CTL_MOD; 85 | 86 | state->postgres_event.events = events; 87 | state->postgres_event.data.ptr = (void *) callback; 88 | return epoll_ctl(state->epoll_fd, 89 | op, 90 | PQsocket(state->postgres_connection), 91 | &state->postgres_event); 92 | } 93 | 94 | //---------------------------------------------------------------------------- 95 | // create and initialize a timerfd for use with poll 96 | // return the fd on success, -1 on error 97 | static int 98 | create_and_set_timer(time_t timer_period) { 99 | //---------------------------------------------------------------------------- 100 | 101 | // create the timer fd 102 | int timerfd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC | TFD_NONBLOCK); 103 | check(timerfd != -1, "timerfd_create"); 104 | 105 | // define the firing interval 106 | struct itimerspec timer_value; 107 | timer_value.it_interval.tv_sec = timer_period; 108 | timer_value.it_interval.tv_nsec = 0; 109 | timer_value.it_value.tv_sec = timer_period; // first expiration 110 | timer_value.it_value.tv_nsec = 0; 111 | 112 | // set the firing interval 113 | int result = timerfd_settime(timerfd, 0, &timer_value, NULL); 114 | check(result == 0, "timerfd_settime"); 115 | 116 | return timerfd; 117 | 118 | error: 119 | 120 | return -1; 121 | } 122 | 123 | 124 | //---------------------------------------------------------------------------- 125 | // find the position of the channel name in config->channel_list 126 | // this is the corresponding position in state->channel_counts 127 | // TODO: if we sort the channel names we can do a binary search 128 | int 129 | _find_channel_index(const struct Config * config, const bstring channel) { 130 | //---------------------------------------------------------------------------- 131 | int i; 132 | for (i=0; i < config->channel_list->qty; i++) { 133 | if (bstrcmp(channel, config->channel_list->entry[i]) == 0) { 134 | return i; 135 | } 136 | } 137 | return -1; 138 | } 139 | 140 | //---------------------------------------------------------------------------- 141 | CALLBACK_RESULT_TYPE 142 | check_notifications_cb(const struct Config * config, struct State * state) { 143 | //---------------------------------------------------------------------------- 144 | (void) config; // unused 145 | PGnotify * notification; 146 | int message_list_size; 147 | struct bstrList * message_list; 148 | int channel_index = -1; 149 | 150 | ConnStatusType status = PQstatus(state->postgres_connection); 151 | check(status == CONNECTION_OK, 152 | "Invalid status in callback '%s'", CONN_STATUS[status]); 153 | 154 | if (PQconsumeInput(state->postgres_connection) != 1) { 155 | log_err("PQconsumeInput %s", 156 | PQerrorMessage(state->postgres_connection)); 157 | return CALLBACK_DATABASE_ERROR; 158 | } 159 | 160 | bool more_notifications = true; 161 | while (more_notifications) { 162 | notification = PQnotifies(state->postgres_connection); 163 | if (notification != NULL) { 164 | 165 | // build the message list 166 | message_list = bstrListCreate(); 167 | check(message_list != NULL, "bstrListCreate"); 168 | 169 | message_list_size = (notification->extra == NULL) ? 2 : 3; 170 | check(bstrListAlloc(message_list, message_list_size) == BSTR_OK, 171 | "bstrListAlloc"); 172 | 173 | // first message: topic 174 | message_list->entry[0] = bfromcstr(notification->relname); 175 | check(message_list->entry[0] != NULL, "bfromcstr 0"); 176 | 177 | // second message: meta data 178 | channel_index = _find_channel_index(config, message_list->entry[0]); 179 | check(channel_index != -1, "channel_index"); 180 | state->channel_counts[channel_index]++; 181 | debug("%s %ld", 182 | notification->relname, 183 | state->channel_counts[channel_index]); 184 | message_list->entry[1] = \ 185 | bformat("timestamp=%d;sequence=%d", 186 | time(NULL), 187 | state->channel_counts[channel_index]); 188 | check(message_list->entry[1] != NULL, "bformat"); 189 | 190 | // third message: data (if present) 191 | if (notification->extra != NULL) { 192 | message_list->entry[2] = bfromcstr(notification->extra); 193 | check(message_list->entry[2] != NULL, "bfromcstr 2"); 194 | } 195 | message_list->qty = message_list_size; 196 | 197 | PQfreemem(notification); 198 | 199 | // publish the message list 200 | check(publish_message(message_list, state->zmq_pub_socket) == 0, 201 | "publish_message"); 202 | 203 | // clean up the message list 204 | check(bstrListDestroy(message_list) == BSTR_OK, "bstrListDestroy"); 205 | 206 | } else { 207 | more_notifications = false; 208 | } 209 | } 210 | 211 | return CALLBACK_OK; 212 | 213 | error: 214 | 215 | return CALLBACK_ERROR; 216 | } 217 | 218 | //---------------------------------------------------------------------------- 219 | CALLBACK_RESULT_TYPE 220 | check_listen_command_cb(const struct Config * config, struct State * state) { 221 | //---------------------------------------------------------------------------- 222 | (void) config; // unused 223 | PGresult * result = NULL; 224 | int ctl_result; 225 | 226 | ConnStatusType status = PQstatus(state->postgres_connection); 227 | if (status != CONNECTION_OK) { 228 | log_err("Invalid status in callback '%s'", CONN_STATUS[status]); 229 | return CALLBACK_DATABASE_ERROR; 230 | } 231 | 232 | result = PQgetResult(state->postgres_connection); 233 | if (result == NULL) { 234 | ctl_result = set_epoll_ctl_for_postgres(EPOLL_READ, 235 | check_notifications_cb, 236 | state); 237 | check(ctl_result == 0, "query complete"); 238 | } else { 239 | PQclear(result); 240 | if (PQconsumeInput(state->postgres_connection) != 1) { 241 | log_err("PQconsumeInput %s", 242 | PQerrorMessage(state->postgres_connection)); 243 | return CALLBACK_DATABASE_ERROR; 244 | } 245 | } 246 | 247 | return CALLBACK_OK; 248 | 249 | error: 250 | 251 | if (result != NULL) PQclear(result); 252 | return CALLBACK_ERROR; 253 | } 254 | 255 | //---------------------------------------------------------------------------- 256 | int 257 | send_listen_command(const struct Config * config, struct State * state) { 258 | //---------------------------------------------------------------------------- 259 | bstring bquery = NULL; 260 | bstring item = NULL; 261 | const char * item_str; 262 | const char * query = NULL; 263 | int i; 264 | 265 | ConnStatusType status = PQstatus(state->postgres_connection); 266 | check(status == CONNECTION_OK, 267 | "Invalid status '%s'", CONN_STATUS[status]); 268 | 269 | bquery = bfromcstr(""); 270 | for (i=0; i < config->channel_list->qty; i++) { 271 | item_str = bstr2cstr(config->channel_list->entry[i], '?'); 272 | check_mem(item_str); 273 | item = bformat("LISTEN %s;", item_str); 274 | check(bcstrfree((char *) item_str) == BSTR_OK, "bcstrfree"); 275 | check(bconcat(bquery, item) == BSTR_OK, "bconcat"); 276 | check(bdestroy(item) == BSTR_OK, "bdestroy(item)"); 277 | } 278 | query = bstr2cstr(bquery, '?'); 279 | check_mem(query); 280 | 281 | debug("query = %s", query); 282 | check(PQsendQuery(state->postgres_connection, query) == 1, 283 | "PQsendQuery"); 284 | 285 | bdestroy(bquery); 286 | bcstrfree((char *) query); 287 | 288 | return 0; 289 | 290 | error: 291 | 292 | bdestroy(bquery); 293 | bcstrfree((char *) query); 294 | return 1; 295 | } 296 | 297 | //---------------------------------------------------------------------------- 298 | // send the heartbeat message 299 | // return 0 on success, 1 on failure 300 | CALLBACK_RESULT_TYPE 301 | heartbeat_timer_cb(const struct Config * config, struct State * state) { 302 | //---------------------------------------------------------------------------- 303 | (void) config; // unused 304 | int message_list_size = 2; 305 | struct bstrList * message_list; 306 | uint64_t expiration_count = 0; 307 | ssize_t bytes_read = read(state->heartbeat_timer_fd, 308 | &expiration_count, 309 | sizeof(expiration_count)); 310 | check(bytes_read == sizeof(expiration_count), "read timerfd"); 311 | 312 | // build the message list 313 | message_list = bstrListCreate(); 314 | check(message_list != NULL, "bstrListCreate"); 315 | 316 | check(bstrListAlloc(message_list, message_list_size) == BSTR_OK, 317 | "bstrListAlloc"); 318 | 319 | // first message is topic 320 | message_list->entry[0] = bfromcstr("heartbeat"); 321 | check(message_list->entry[0] != NULL, "bfromcstr 0"); 322 | 323 | // second message is meta data 324 | state->heartbeat_count++; 325 | debug("heartbeat %ld", state->heartbeat_count); 326 | message_list->entry[1] = \ 327 | bformat("timestamp=%d;sequence=%d;connected=%d", 328 | time(NULL), 329 | state->heartbeat_count, 330 | state->postgres_connect_time); 331 | check(message_list->entry[1] != NULL, "bformat"); 332 | message_list->qty = message_list_size; 333 | 334 | // publish the message list 335 | check(publish_message(message_list, state->zmq_pub_socket) == 0, 336 | "publish_message"); 337 | 338 | // clean up the message list 339 | check(bstrListDestroy(message_list) == BSTR_OK, "bstrListDestroy"); 340 | 341 | return CALLBACK_OK; 342 | 343 | error: 344 | 345 | return CALLBACK_ERROR; 346 | } 347 | 348 | //---------------------------------------------------------------------------- 349 | // try to restart the postgres connection 350 | // return 0 on success, 1 on failure 351 | CALLBACK_RESULT_TYPE 352 | restart_timer_cb(const struct Config * config, struct State * state) { 353 | //---------------------------------------------------------------------------- 354 | (void) config; // unused 355 | int result; 356 | uint64_t expiration_count = 0; 357 | ssize_t bytes_read = read(state->restart_timer_fd, 358 | &expiration_count, 359 | sizeof(expiration_count)); 360 | check(bytes_read == sizeof(expiration_count), "read timerfd"); 361 | debug("restart timer fired expiration_count = %ld", expiration_count); 362 | 363 | // turn off the restart timer 364 | result = epoll_ctl(state->epoll_fd, 365 | EPOLL_CTL_DEL, 366 | state->restart_timer_fd, 367 | &state->restart_timer_event); 368 | check(result == 0, "epoll restart timer"); 369 | check(close(state->restart_timer_fd) == 0, "close"); 370 | 371 | if (start_postgres_connection(config, state) != 0) { 372 | return CALLBACK_DATABASE_ERROR; 373 | } 374 | 375 | return CALLBACK_OK; 376 | 377 | error: 378 | 379 | return CALLBACK_ERROR; 380 | } 381 | 382 | //---------------------------------------------------------------------------- 383 | CALLBACK_RESULT_TYPE 384 | postgres_connection_cb(const struct Config * config, struct State * state) { 385 | //---------------------------------------------------------------------------- 386 | 387 | PostgresPollingStatusType polling_status; 388 | int ctl_result; 389 | 390 | polling_status = PQconnectPoll(state->postgres_connection); 391 | 392 | switch (polling_status) { 393 | case PGRES_POLLING_READING: 394 | ctl_result = set_epoll_ctl_for_postgres(EPOLL_READ, 395 | postgres_connection_cb, 396 | state); 397 | check(ctl_result == 0, "postgres_connection_cb"); 398 | break; 399 | 400 | case PGRES_POLLING_WRITING: 401 | ctl_result = set_epoll_ctl_for_postgres(EPOLL_WRITE, 402 | postgres_connection_cb, 403 | state); 404 | check(ctl_result == 0, "postgres_connection_cb"); 405 | break; 406 | 407 | case PGRES_POLLING_OK: 408 | state->postgres_connect_time = time(NULL); 409 | check(send_listen_command(config, state) == 0, "send_listen_command"); 410 | ctl_result = set_epoll_ctl_for_postgres(EPOLL_READ, 411 | check_listen_command_cb, 412 | state); 413 | check(ctl_result == 0, "postgres_connection_cb"); 414 | break; 415 | 416 | default: 417 | log_err("invalid Postgres Polling Status %s postgres_connection_cb", 418 | POLLING_STATUS[polling_status]); 419 | return CALLBACK_DATABASE_ERROR; 420 | 421 | } //switch 422 | 423 | return CALLBACK_OK; 424 | 425 | error: 426 | return CALLBACK_ERROR; 427 | } 428 | 429 | 430 | //---------------------------------------------------------------------------- 431 | // start the asynchronous connection process 432 | // returns 0 on success, 1 on failure 433 | int 434 | start_postgres_connection(const struct Config * config, struct State * state) { 435 | //---------------------------------------------------------------------------- 436 | PostgresPollingStatusType polling_status; 437 | int ctl_result; 438 | 439 | state->postgres_connection = \ 440 | PQconnectStartParams(config->postgresql_keywords, 441 | config->postgresql_values, 442 | 0); 443 | check(state->postgres_connection != NULL, "PQconnectStartParams"); 444 | check(PQstatus(state->postgres_connection) != CONNECTION_BAD, 445 | "CONNECTION_BAD"); 446 | 447 | polling_status = PQconnectPoll(state->postgres_connection); 448 | switch (polling_status) { 449 | 450 | case PGRES_POLLING_READING: 451 | ctl_result = set_epoll_ctl_for_postgres(EPOLL_READ, 452 | postgres_connection_cb, 453 | state); 454 | check(ctl_result == 0, "start_postgres_connection"); 455 | break; 456 | 457 | case PGRES_POLLING_WRITING: 458 | ctl_result = set_epoll_ctl_for_postgres(EPOLL_WRITE, 459 | postgres_connection_cb, 460 | state); 461 | check(ctl_result == 0, "start_postgres_connection"); 462 | break; 463 | 464 | default: 465 | sentinel("invalid Postgres Polling Status %s", 466 | POLLING_STATUS[polling_status]); 467 | } 468 | 469 | return 0; 470 | 471 | error: 472 | 473 | return 1; 474 | } 475 | 476 | 477 | //---------------------------------------------------------------------------- 478 | int 479 | initialize_state(const struct Config * config, 480 | void * zmq_context, 481 | struct State * state) { 482 | //---------------------------------------------------------------------------- 483 | char * pub_socket_uri = NULL; 484 | int result; 485 | 486 | state->heartbeat_timer_fd = \ 487 | create_and_set_timer(config->heartbeat_interval); 488 | check(state->heartbeat_timer_fd != -1, "create_and_set_timer"); 489 | state->heartbeat_timer_event.events = EPOLLIN | EPOLLERR; 490 | state->heartbeat_timer_event.data.ptr = (void *) heartbeat_timer_cb; 491 | 492 | state->restart_timer_fd = -1; 493 | state->restart_timer_event.events = EPOLLIN | EPOLLERR; 494 | state->restart_timer_event.data.ptr = (void *) restart_timer_cb; 495 | 496 | state->postgres_connection = NULL; 497 | state->postgres_event.events = 0; 498 | state->postgres_event.data.ptr = NULL; 499 | 500 | state->epoll_fd = epoll_create(1); 501 | check(state->epoll_fd != -1, "epoll_create"); 502 | 503 | state->zmq_pub_socket = zmq_socket(zmq_context, ZMQ_PUB); 504 | check(state->zmq_pub_socket != NULL, "zmq_socket"); 505 | 506 | result = zmq_setsockopt(state->zmq_pub_socket, 507 | ZMQ_SNDHWM, 508 | &config->pub_socket_hwm, 509 | sizeof config->pub_socket_hwm); 510 | check(result == 0, "zmq_setsockopt"); 511 | 512 | log_info("binding PUB socket to '%s'", config->pub_socket_uri); 513 | check(zmq_bind(state->zmq_pub_socket, config->pub_socket_uri) == 0, 514 | "bind %s", 515 | config->pub_socket_uri); 516 | 517 | return 0; 518 | 519 | error: 520 | 521 | if (pub_socket_uri != NULL) bcstrfree(pub_socket_uri); 522 | return 1; 523 | } 524 | 525 | //---------------------------------------------------------------------------- 526 | // start the retry timer to re-try connecting to the database 527 | int 528 | set_up_database_retry(const struct Config * config, struct State * state) { 529 | //---------------------------------------------------------------------------- 530 | int result; 531 | 532 | // don't check the state here, our socket fd may be no good 533 | epoll_ctl(state->epoll_fd, 534 | EPOLL_CTL_DEL, 535 | PQsocket(state->postgres_connection), 536 | &state->postgres_event); 537 | state->postgres_event.events = 0; 538 | 539 | PQfinish(state->postgres_connection); 540 | state->postgres_connection = NULL; 541 | state->postgres_connect_time = 0; 542 | 543 | state->restart_timer_fd = \ 544 | create_and_set_timer(config->database_retry_interval); 545 | check(state->restart_timer_fd != -1, "create_and_set_timer"); 546 | state->restart_timer_event.events = EPOLLIN | EPOLLERR; 547 | state->restart_timer_event.data.ptr = (void *) restart_timer_cb; 548 | 549 | result = epoll_ctl(state->epoll_fd, 550 | EPOLL_CTL_ADD, 551 | state->restart_timer_fd, 552 | &state->restart_timer_event); 553 | check(result == 0, "epoll restart timer"); 554 | 555 | return 0; 556 | error: 557 | return -1; 558 | } 559 | 560 | //---------------------------------------------------------------------------- 561 | int 562 | main(int argc, char **argv, char **envp) { 563 | //---------------------------------------------------------------------------- 564 | (void) envp; // unused 565 | bstring config_path = NULL; 566 | const struct Config * config = NULL; 567 | struct State * state = NULL; 568 | void *zmq_context = NULL; 569 | int result; 570 | CALLBACK_RESULT_TYPE callback_result; 571 | struct epoll_event event_list[MAX_EPOLL_EVENTS]; 572 | int i; 573 | 574 | #if defined(NDEBUG) 575 | openlog(PROGRAM_NAME, LOG_CONS | LOG_PERROR, LOG_USER); 576 | #endif 577 | 578 | log_info("program starts"); 579 | 580 | check(parse_command_line(argc, argv, &config_path) == 0, "parse_"); 581 | if (blength(config_path) == 0) { 582 | check(compute_default_config_path(&config_path) == 0, "default config"); 583 | } 584 | 585 | // initilize our basic structs 586 | config = load_config(config_path); 587 | check(config != NULL, "load_config"); 588 | state = create_state(config); 589 | check(state != NULL, "create_state"); 590 | 591 | zmq_context = zmq_init(config->zmq_thread_pool_size); 592 | check(zmq_context != NULL, "initializing zeromq"); 593 | 594 | check(initialize_state(config, zmq_context, state) == 0, "initialize_state"); 595 | 596 | // start polling the heartbeat timer 597 | result = epoll_ctl(state->epoll_fd, 598 | EPOLL_CTL_ADD, 599 | state->heartbeat_timer_fd, 600 | &state->heartbeat_timer_event); 601 | check(result == 0, "epoll heartbeat timer"); 602 | 603 | // start postgres connection process 604 | if (start_postgres_connection(config, state) != 0) { 605 | log_err("unable to start posgres connection"); 606 | check(set_up_database_retry(config, state) == 0, "retry"); 607 | } 608 | 609 | // main epoll loop, using callbacks to drive he program 610 | check(install_signal_handler() == 0, "install signal handler"); 611 | while (!halt_signal) { 612 | result = epoll_wait(state->epoll_fd, 613 | event_list, 614 | MAX_EPOLL_EVENTS, 615 | config->epoll_timeout * 1000); 616 | // we can get 'interrupted system call' from zeromq at shutdown 617 | // we don't treat it as an error 618 | check(halt_signal || result != -1, "epoll_wait") 619 | if (result == 0) { 620 | continue; 621 | } 622 | for (i=0; i < result; i++) { 623 | check(event_list[i].data.ptr != NULL, "NULL callback"); 624 | callback_result = \ 625 | ((epoll_callback) event_list[i].data.ptr)(config, state); 626 | if (callback_result == CALLBACK_DATABASE_ERROR) { 627 | log_err("database error"); 628 | check(set_up_database_retry(config, state) == 0, "retry"); 629 | } else { 630 | check(callback_result == CALLBACK_OK, "callback"); 631 | } 632 | } 633 | } // while 634 | debug("while loop broken"); 635 | 636 | clear_state(state); 637 | clear_config(config); 638 | check(zmq_term(zmq_context) == 0, "terminating zeromq") 639 | check(bdestroy(config_path) == BSTR_OK, "bdestroy"); 640 | log_info("program terminates normally"); 641 | #if defined(NDEBUG) 642 | closelog(); 643 | #endif 644 | return 0; 645 | 646 | error: 647 | if (state != NULL) clear_state(state); 648 | if (config != NULL) clear_config(config); 649 | if (zmq_context != NULL) zmq_term(zmq_context); 650 | log_info("program terminates with error"); 651 | #if defined(NDEBUG) 652 | closelog(); 653 | #endif 654 | return 1; 655 | } 656 | -------------------------------------------------------------------------------- /src/bstrlib.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the bstring string library. This code was 3 | * written by Paul Hsieh in 2002-2010, and is covered by either the 3-clause 4 | * BSD open source license or GPL v2.0. Refer to the accompanying documentation 5 | * for details on usage and license. 6 | */ 7 | 8 | /* 9 | * bstrlib.c 10 | * 11 | * This file is the core module for implementing the bstring functions. 12 | */ 13 | 14 | #if defined (_MSC_VER) 15 | /* These warnings from MSVC++ are totally pointless. */ 16 | # define _CRT_SECURE_NO_WARNINGS 17 | #endif 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "bstrlib.h" 26 | 27 | /* Optionally include a mechanism for debugging memory */ 28 | 29 | #if defined(MEMORY_DEBUG) || defined(BSTRLIB_MEMORY_DEBUG) 30 | #include "memdbg.h" 31 | #endif 32 | 33 | #ifndef bstr__alloc 34 | #define bstr__alloc(x) malloc (x) 35 | #endif 36 | 37 | #ifndef bstr__free 38 | #define bstr__free(p) free (p) 39 | #endif 40 | 41 | #ifndef bstr__realloc 42 | #define bstr__realloc(p,x) realloc ((p), (x)) 43 | #endif 44 | 45 | #ifndef bstr__memcpy 46 | #define bstr__memcpy(d,s,l) memcpy ((d), (s), (l)) 47 | #endif 48 | 49 | #ifndef bstr__memmove 50 | #define bstr__memmove(d,s,l) memmove ((d), (s), (l)) 51 | #endif 52 | 53 | #ifndef bstr__memset 54 | #define bstr__memset(d,c,l) memset ((d), (c), (l)) 55 | #endif 56 | 57 | #ifndef bstr__memcmp 58 | #define bstr__memcmp(d,c,l) memcmp ((d), (c), (l)) 59 | #endif 60 | 61 | #ifndef bstr__memchr 62 | #define bstr__memchr(s,c,l) memchr ((s), (c), (l)) 63 | #endif 64 | 65 | /* Just a length safe wrapper for memmove. */ 66 | 67 | #define bBlockCopy(D,S,L) { if ((L) > 0) bstr__memmove ((D),(S),(L)); } 68 | 69 | /* Compute the snapped size for a given requested size. By snapping to powers 70 | of 2 like this, repeated reallocations are avoided. */ 71 | static int snapUpSize (int i) { 72 | if (i < 8) { 73 | i = 8; 74 | } else { 75 | unsigned int j; 76 | j = (unsigned int) i; 77 | 78 | j |= (j >> 1); 79 | j |= (j >> 2); 80 | j |= (j >> 4); 81 | j |= (j >> 8); /* Ok, since int >= 16 bits */ 82 | #if (UINT_MAX != 0xffff) 83 | j |= (j >> 16); /* For 32 bit int systems */ 84 | #if (UINT_MAX > 0xffffffffUL) 85 | j |= (j >> 32); /* For 64 bit int systems */ 86 | #endif 87 | #endif 88 | /* Least power of two greater than i */ 89 | j++; 90 | if ((int) j >= i) i = (int) j; 91 | } 92 | return i; 93 | } 94 | 95 | /* int balloc (bstring b, int len) 96 | * 97 | * Increase the size of the memory backing the bstring b to at least len. 98 | */ 99 | int balloc (bstring b, int olen) { 100 | int len; 101 | if (b == NULL || b->data == NULL || b->slen < 0 || b->mlen <= 0 || 102 | b->mlen < b->slen || olen <= 0) { 103 | return BSTR_ERR; 104 | } 105 | 106 | if (olen >= b->mlen) { 107 | unsigned char * x; 108 | 109 | if ((len = snapUpSize (olen)) <= b->mlen) return BSTR_OK; 110 | 111 | /* Assume probability of a non-moving realloc is 0.125 */ 112 | if (7 * b->mlen < 8 * b->slen) { 113 | 114 | /* If slen is close to mlen in size then use realloc to reduce 115 | the memory defragmentation */ 116 | 117 | reallocStrategy:; 118 | 119 | x = (unsigned char *) bstr__realloc (b->data, (size_t) len); 120 | if (x == NULL) { 121 | 122 | /* Since we failed, try allocating the tighest possible 123 | allocation */ 124 | 125 | if (NULL == (x = (unsigned char *) bstr__realloc (b->data, (size_t) (len = olen)))) { 126 | return BSTR_ERR; 127 | } 128 | } 129 | } else { 130 | 131 | /* If slen is not close to mlen then avoid the penalty of copying 132 | the extra bytes that are allocated, but not considered part of 133 | the string */ 134 | 135 | if (NULL == (x = (unsigned char *) bstr__alloc ((size_t) len))) { 136 | 137 | /* Perhaps there is no available memory for the two 138 | allocations to be in memory at once */ 139 | 140 | goto reallocStrategy; 141 | 142 | } else { 143 | if (b->slen) bstr__memcpy ((char *) x, (char *) b->data, (size_t) b->slen); 144 | bstr__free (b->data); 145 | } 146 | } 147 | b->data = x; 148 | b->mlen = len; 149 | b->data[b->slen] = (unsigned char) '\0'; 150 | } 151 | 152 | return BSTR_OK; 153 | } 154 | 155 | /* int ballocmin (bstring b, int len) 156 | * 157 | * Set the size of the memory backing the bstring b to len or b->slen+1, 158 | * whichever is larger. Note that repeated use of this function can degrade 159 | * performance. 160 | */ 161 | int ballocmin (bstring b, int len) { 162 | unsigned char * s; 163 | 164 | if (b == NULL || b->data == NULL || (b->slen+1) < 0 || b->mlen <= 0 || 165 | b->mlen < b->slen || len <= 0) { 166 | return BSTR_ERR; 167 | } 168 | 169 | if (len < b->slen + 1) len = b->slen + 1; 170 | 171 | if (len != b->mlen) { 172 | s = (unsigned char *) bstr__realloc (b->data, (size_t) len); 173 | if (NULL == s) return BSTR_ERR; 174 | s[b->slen] = (unsigned char) '\0'; 175 | b->data = s; 176 | b->mlen = len; 177 | } 178 | 179 | return BSTR_OK; 180 | } 181 | 182 | /* bstring bfromcstr (const char * str) 183 | * 184 | * Create a bstring which contains the contents of the '\0' terminated char * 185 | * buffer str. 186 | */ 187 | bstring bfromcstr (const char * str) { 188 | bstring b; 189 | int i; 190 | size_t j; 191 | 192 | if (str == NULL) return NULL; 193 | j = (strlen) (str); 194 | i = snapUpSize ((int) (j + (2 - (j != 0)))); 195 | if (i <= (int) j) return NULL; 196 | 197 | b = (bstring) bstr__alloc (sizeof (struct tagbstring)); 198 | if (NULL == b) return NULL; 199 | b->slen = (int) j; 200 | if (NULL == (b->data = (unsigned char *) bstr__alloc (b->mlen = i))) { 201 | bstr__free (b); 202 | return NULL; 203 | } 204 | 205 | bstr__memcpy (b->data, str, j+1); 206 | return b; 207 | } 208 | 209 | /* bstring bfromcstralloc (int mlen, const char * str) 210 | * 211 | * Create a bstring which contains the contents of the '\0' terminated char * 212 | * buffer str. The memory buffer backing the string is at least len 213 | * characters in length. 214 | */ 215 | bstring bfromcstralloc (int mlen, const char * str) { 216 | bstring b; 217 | int i; 218 | size_t j; 219 | 220 | if (str == NULL) return NULL; 221 | j = (strlen) (str); 222 | i = snapUpSize ((int) (j + (2 - (j != 0)))); 223 | if (i <= (int) j) return NULL; 224 | 225 | b = (bstring) bstr__alloc (sizeof (struct tagbstring)); 226 | if (b == NULL) return NULL; 227 | b->slen = (int) j; 228 | if (i < mlen) i = mlen; 229 | 230 | if (NULL == (b->data = (unsigned char *) bstr__alloc (b->mlen = i))) { 231 | bstr__free (b); 232 | return NULL; 233 | } 234 | 235 | bstr__memcpy (b->data, str, j+1); 236 | return b; 237 | } 238 | 239 | /* bstring blk2bstr (const void * blk, int len) 240 | * 241 | * Create a bstring which contains the content of the block blk of length 242 | * len. 243 | */ 244 | bstring blk2bstr (const void * blk, int len) { 245 | bstring b; 246 | int i; 247 | 248 | if (blk == NULL || len < 0) return NULL; 249 | b = (bstring) bstr__alloc (sizeof (struct tagbstring)); 250 | if (b == NULL) return NULL; 251 | b->slen = len; 252 | 253 | i = len + (2 - (len != 0)); 254 | i = snapUpSize (i); 255 | 256 | b->mlen = i; 257 | 258 | b->data = (unsigned char *) bstr__alloc ((size_t) b->mlen); 259 | if (b->data == NULL) { 260 | bstr__free (b); 261 | return NULL; 262 | } 263 | 264 | if (len > 0) bstr__memcpy (b->data, blk, (size_t) len); 265 | b->data[len] = (unsigned char) '\0'; 266 | 267 | return b; 268 | } 269 | 270 | /* char * bstr2cstr (const_bstring s, char z) 271 | * 272 | * Create a '\0' terminated char * buffer which is equal to the contents of 273 | * the bstring s, except that any contained '\0' characters are converted 274 | * to the character in z. This returned value should be freed with a 275 | * bcstrfree () call, by the calling application. 276 | */ 277 | char * bstr2cstr (const_bstring b, char z) { 278 | int i, l; 279 | char * r; 280 | 281 | if (b == NULL || b->slen < 0 || b->data == NULL) return NULL; 282 | l = b->slen; 283 | r = (char *) bstr__alloc ((size_t) (l + 1)); 284 | if (r == NULL) return r; 285 | 286 | for (i=0; i < l; i ++) { 287 | r[i] = (char) ((b->data[i] == '\0') ? z : (char) (b->data[i])); 288 | } 289 | 290 | r[l] = (unsigned char) '\0'; 291 | 292 | return r; 293 | } 294 | 295 | /* int bcstrfree (char * s) 296 | * 297 | * Frees a C-string generated by bstr2cstr (). This is normally unnecessary 298 | * since it just wraps a call to bstr__free (), however, if bstr__alloc () 299 | * and bstr__free () have been redefined as a macros within the bstrlib 300 | * module (via defining them in memdbg.h after defining 301 | * BSTRLIB_MEMORY_DEBUG) with some difference in behaviour from the std 302 | * library functions, then this allows a correct way of freeing the memory 303 | * that allows higher level code to be independent from these macro 304 | * redefinitions. 305 | */ 306 | int bcstrfree (char * s) { 307 | if (s) { 308 | bstr__free (s); 309 | return BSTR_OK; 310 | } 311 | return BSTR_ERR; 312 | } 313 | 314 | /* int bconcat (bstring b0, const_bstring b1) 315 | * 316 | * Concatenate the bstring b1 to the bstring b0. 317 | */ 318 | int bconcat (bstring b0, const_bstring b1) { 319 | int len, d; 320 | bstring aux = (bstring) b1; 321 | 322 | if (b0 == NULL || b1 == NULL || b0->data == NULL || b1->data == NULL) return BSTR_ERR; 323 | 324 | d = b0->slen; 325 | len = b1->slen; 326 | if ((d | (b0->mlen - d) | len | (d + len)) < 0) return BSTR_ERR; 327 | 328 | if (b0->mlen <= d + len + 1) { 329 | ptrdiff_t pd = b1->data - b0->data; 330 | if (0 <= pd && pd < b0->mlen) { 331 | if (NULL == (aux = bstrcpy (b1))) return BSTR_ERR; 332 | } 333 | if (balloc (b0, d + len + 1) != BSTR_OK) { 334 | if (aux != b1) bdestroy (aux); 335 | return BSTR_ERR; 336 | } 337 | } 338 | 339 | bBlockCopy (&b0->data[d], &aux->data[0], (size_t) len); 340 | b0->data[d + len] = (unsigned char) '\0'; 341 | b0->slen = d + len; 342 | if (aux != b1) bdestroy (aux); 343 | return BSTR_OK; 344 | } 345 | 346 | /* int bconchar (bstring b, char c) 347 | / * 348 | * Concatenate the single character c to the bstring b. 349 | */ 350 | int bconchar (bstring b, char c) { 351 | int d; 352 | 353 | if (b == NULL) return BSTR_ERR; 354 | d = b->slen; 355 | if ((d | (b->mlen - d)) < 0 || balloc (b, d + 2) != BSTR_OK) return BSTR_ERR; 356 | b->data[d] = (unsigned char) c; 357 | b->data[d + 1] = (unsigned char) '\0'; 358 | b->slen++; 359 | return BSTR_OK; 360 | } 361 | 362 | /* int bcatcstr (bstring b, const char * s) 363 | * 364 | * Concatenate a char * string to a bstring. 365 | */ 366 | int bcatcstr (bstring b, const char * s) { 367 | char * d; 368 | int i, l; 369 | 370 | if (b == NULL || b->data == NULL || b->slen < 0 || b->mlen < b->slen 371 | || b->mlen <= 0 || s == NULL) return BSTR_ERR; 372 | 373 | /* Optimistically concatenate directly */ 374 | l = b->mlen - b->slen; 375 | d = (char *) &b->data[b->slen]; 376 | for (i=0; i < l; i++) { 377 | if ((*d++ = *s++) == '\0') { 378 | b->slen += i; 379 | return BSTR_OK; 380 | } 381 | } 382 | b->slen += i; 383 | 384 | /* Need to explicitely resize and concatenate tail */ 385 | return bcatblk (b, (const void *) s, (int) strlen (s)); 386 | } 387 | 388 | /* int bcatblk (bstring b, const void * s, int len) 389 | * 390 | * Concatenate a fixed length buffer to a bstring. 391 | */ 392 | int bcatblk (bstring b, const void * s, int len) { 393 | int nl; 394 | 395 | if (b == NULL || b->data == NULL || b->slen < 0 || b->mlen < b->slen 396 | || b->mlen <= 0 || s == NULL || len < 0) return BSTR_ERR; 397 | 398 | if (0 > (nl = b->slen + len)) return BSTR_ERR; /* Overflow? */ 399 | if (b->mlen <= nl && 0 > balloc (b, nl + 1)) return BSTR_ERR; 400 | 401 | bBlockCopy (&b->data[b->slen], s, (size_t) len); 402 | b->slen = nl; 403 | b->data[nl] = (unsigned char) '\0'; 404 | return BSTR_OK; 405 | } 406 | 407 | /* bstring bstrcpy (const_bstring b) 408 | * 409 | * Create a copy of the bstring b. 410 | */ 411 | bstring bstrcpy (const_bstring b) { 412 | bstring b0; 413 | int i,j; 414 | 415 | /* Attempted to copy an invalid string? */ 416 | if (b == NULL || b->slen < 0 || b->data == NULL) return NULL; 417 | 418 | b0 = (bstring) bstr__alloc (sizeof (struct tagbstring)); 419 | if (b0 == NULL) { 420 | /* Unable to allocate memory for string header */ 421 | return NULL; 422 | } 423 | 424 | i = b->slen; 425 | j = snapUpSize (i + 1); 426 | 427 | b0->data = (unsigned char *) bstr__alloc (j); 428 | if (b0->data == NULL) { 429 | j = i + 1; 430 | b0->data = (unsigned char *) bstr__alloc (j); 431 | if (b0->data == NULL) { 432 | /* Unable to allocate memory for string data */ 433 | bstr__free (b0); 434 | return NULL; 435 | } 436 | } 437 | 438 | b0->mlen = j; 439 | b0->slen = i; 440 | 441 | if (i) bstr__memcpy ((char *) b0->data, (char *) b->data, i); 442 | b0->data[b0->slen] = (unsigned char) '\0'; 443 | 444 | return b0; 445 | } 446 | 447 | /* int bassign (bstring a, const_bstring b) 448 | * 449 | * Overwrite the string a with the contents of string b. 450 | */ 451 | int bassign (bstring a, const_bstring b) { 452 | if (b == NULL || b->data == NULL || b->slen < 0) 453 | return BSTR_ERR; 454 | if (b->slen != 0) { 455 | if (balloc (a, b->slen) != BSTR_OK) return BSTR_ERR; 456 | bstr__memmove (a->data, b->data, b->slen); 457 | } else { 458 | if (a == NULL || a->data == NULL || a->mlen < a->slen || 459 | a->slen < 0 || a->mlen == 0) 460 | return BSTR_ERR; 461 | } 462 | a->data[b->slen] = (unsigned char) '\0'; 463 | a->slen = b->slen; 464 | return BSTR_OK; 465 | } 466 | 467 | /* int bassignmidstr (bstring a, const_bstring b, int left, int len) 468 | * 469 | * Overwrite the string a with the middle of contents of string b 470 | * starting from position left and running for a length len. left and 471 | * len are clamped to the ends of b as with the function bmidstr. 472 | */ 473 | int bassignmidstr (bstring a, const_bstring b, int left, int len) { 474 | if (b == NULL || b->data == NULL || b->slen < 0) 475 | return BSTR_ERR; 476 | 477 | if (left < 0) { 478 | len += left; 479 | left = 0; 480 | } 481 | 482 | if (len > b->slen - left) len = b->slen - left; 483 | 484 | if (a == NULL || a->data == NULL || a->mlen < a->slen || 485 | a->slen < 0 || a->mlen == 0) 486 | return BSTR_ERR; 487 | 488 | if (len > 0) { 489 | if (balloc (a, len) != BSTR_OK) return BSTR_ERR; 490 | bstr__memmove (a->data, b->data + left, len); 491 | a->slen = len; 492 | } else { 493 | a->slen = 0; 494 | } 495 | a->data[a->slen] = (unsigned char) '\0'; 496 | return BSTR_OK; 497 | } 498 | 499 | /* int bassigncstr (bstring a, const char * str) 500 | * 501 | * Overwrite the string a with the contents of char * string str. Note that 502 | * the bstring a must be a well defined and writable bstring. If an error 503 | * occurs BSTR_ERR is returned however a may be partially overwritten. 504 | */ 505 | int bassigncstr (bstring a, const char * str) { 506 | int i; 507 | size_t len; 508 | if (a == NULL || a->data == NULL || a->mlen < a->slen || 509 | a->slen < 0 || a->mlen == 0 || NULL == str) 510 | return BSTR_ERR; 511 | 512 | for (i=0; i < a->mlen; i++) { 513 | if ('\0' == (a->data[i] = str[i])) { 514 | a->slen = i; 515 | return BSTR_OK; 516 | } 517 | } 518 | 519 | a->slen = i; 520 | len = strlen (str + i); 521 | if (len > INT_MAX || i + len + 1 > INT_MAX || 522 | 0 > balloc (a, (int) (i + len + 1))) return BSTR_ERR; 523 | bBlockCopy (a->data + i, str + i, (size_t) len + 1); 524 | a->slen += (int) len; 525 | return BSTR_OK; 526 | } 527 | 528 | /* int bassignblk (bstring a, const void * s, int len) 529 | * 530 | * Overwrite the string a with the contents of the block (s, len). Note that 531 | * the bstring a must be a well defined and writable bstring. If an error 532 | * occurs BSTR_ERR is returned and a is not overwritten. 533 | */ 534 | int bassignblk (bstring a, const void * s, int len) { 535 | if (a == NULL || a->data == NULL || a->mlen < a->slen || 536 | a->slen < 0 || a->mlen == 0 || NULL == s || len + 1 < 1) 537 | return BSTR_ERR; 538 | if (len + 1 > a->mlen && 0 > balloc (a, len + 1)) return BSTR_ERR; 539 | bBlockCopy (a->data, s, (size_t) len); 540 | a->data[len] = (unsigned char) '\0'; 541 | a->slen = len; 542 | return BSTR_OK; 543 | } 544 | 545 | /* int btrunc (bstring b, int n) 546 | * 547 | * Truncate the bstring to at most n characters. 548 | */ 549 | int btrunc (bstring b, int n) { 550 | if (n < 0 || b == NULL || b->data == NULL || b->mlen < b->slen || 551 | b->slen < 0 || b->mlen <= 0) return BSTR_ERR; 552 | if (b->slen > n) { 553 | b->slen = n; 554 | b->data[n] = (unsigned char) '\0'; 555 | } 556 | return BSTR_OK; 557 | } 558 | 559 | #define upcase(c) (toupper ((unsigned char) c)) 560 | #define downcase(c) (tolower ((unsigned char) c)) 561 | #define wspace(c) (isspace ((unsigned char) c)) 562 | 563 | /* int btoupper (bstring b) 564 | * 565 | * Convert contents of bstring to upper case. 566 | */ 567 | int btoupper (bstring b) { 568 | int i, len; 569 | if (b == NULL || b->data == NULL || b->mlen < b->slen || 570 | b->slen < 0 || b->mlen <= 0) return BSTR_ERR; 571 | for (i=0, len = b->slen; i < len; i++) { 572 | b->data[i] = (unsigned char) upcase (b->data[i]); 573 | } 574 | return BSTR_OK; 575 | } 576 | 577 | /* int btolower (bstring b) 578 | * 579 | * Convert contents of bstring to lower case. 580 | */ 581 | int btolower (bstring b) { 582 | int i, len; 583 | if (b == NULL || b->data == NULL || b->mlen < b->slen || 584 | b->slen < 0 || b->mlen <= 0) return BSTR_ERR; 585 | for (i=0, len = b->slen; i < len; i++) { 586 | b->data[i] = (unsigned char) downcase (b->data[i]); 587 | } 588 | return BSTR_OK; 589 | } 590 | 591 | /* int bstricmp (const_bstring b0, const_bstring b1) 592 | * 593 | * Compare two strings without differentiating between case. The return 594 | * value is the difference of the values of the characters where the two 595 | * strings first differ after lower case transformation, otherwise 0 is 596 | * returned indicating that the strings are equal. If the lengths are 597 | * different, then a difference from 0 is given, but if the first extra 598 | * character is '\0', then it is taken to be the value UCHAR_MAX+1. 599 | */ 600 | int bstricmp (const_bstring b0, const_bstring b1) { 601 | int i, v, n; 602 | 603 | if (bdata (b0) == NULL || b0->slen < 0 || 604 | bdata (b1) == NULL || b1->slen < 0) return SHRT_MIN; 605 | if ((n = b0->slen) > b1->slen) n = b1->slen; 606 | else if (b0->slen == b1->slen && b0->data == b1->data) return BSTR_OK; 607 | 608 | for (i = 0; i < n; i ++) { 609 | v = (char) downcase (b0->data[i]) 610 | - (char) downcase (b1->data[i]); 611 | if (0 != v) return v; 612 | } 613 | 614 | if (b0->slen > n) { 615 | v = (char) downcase (b0->data[n]); 616 | if (v) return v; 617 | return UCHAR_MAX + 1; 618 | } 619 | if (b1->slen > n) { 620 | v = - (char) downcase (b1->data[n]); 621 | if (v) return v; 622 | return - (int) (UCHAR_MAX + 1); 623 | } 624 | return BSTR_OK; 625 | } 626 | 627 | /* int bstrnicmp (const_bstring b0, const_bstring b1, int n) 628 | * 629 | * Compare two strings without differentiating between case for at most n 630 | * characters. If the position where the two strings first differ is 631 | * before the nth position, the return value is the difference of the values 632 | * of the characters, otherwise 0 is returned. If the lengths are different 633 | * and less than n characters, then a difference from 0 is given, but if the 634 | * first extra character is '\0', then it is taken to be the value 635 | * UCHAR_MAX+1. 636 | */ 637 | int bstrnicmp (const_bstring b0, const_bstring b1, int n) { 638 | int i, v, m; 639 | 640 | if (bdata (b0) == NULL || b0->slen < 0 || 641 | bdata (b1) == NULL || b1->slen < 0 || n < 0) return SHRT_MIN; 642 | m = n; 643 | if (m > b0->slen) m = b0->slen; 644 | if (m > b1->slen) m = b1->slen; 645 | 646 | if (b0->data != b1->data) { 647 | for (i = 0; i < m; i ++) { 648 | v = (char) downcase (b0->data[i]); 649 | v -= (char) downcase (b1->data[i]); 650 | if (v != 0) return b0->data[i] - b1->data[i]; 651 | } 652 | } 653 | 654 | if (n == m || b0->slen == b1->slen) return BSTR_OK; 655 | 656 | if (b0->slen > m) { 657 | v = (char) downcase (b0->data[m]); 658 | if (v) return v; 659 | return UCHAR_MAX + 1; 660 | } 661 | 662 | v = - (char) downcase (b1->data[m]); 663 | if (v) return v; 664 | return - (int) (UCHAR_MAX + 1); 665 | } 666 | 667 | /* int biseqcaseless (const_bstring b0, const_bstring b1) 668 | * 669 | * Compare two strings for equality without differentiating between case. 670 | * If the strings differ other than in case, 0 is returned, if the strings 671 | * are the same, 1 is returned, if there is an error, -1 is returned. If 672 | * the length of the strings are different, this function is O(1). '\0' 673 | * termination characters are not treated in any special way. 674 | */ 675 | int biseqcaseless (const_bstring b0, const_bstring b1) { 676 | int i, n; 677 | 678 | if (bdata (b0) == NULL || b0->slen < 0 || 679 | bdata (b1) == NULL || b1->slen < 0) return BSTR_ERR; 680 | if (b0->slen != b1->slen) return BSTR_OK; 681 | if (b0->data == b1->data || b0->slen == 0) return 1; 682 | for (i=0, n=b0->slen; i < n; i++) { 683 | if (b0->data[i] != b1->data[i]) { 684 | unsigned char c = (unsigned char) downcase (b0->data[i]); 685 | if (c != (unsigned char) downcase (b1->data[i])) return 0; 686 | } 687 | } 688 | return 1; 689 | } 690 | 691 | /* int bisstemeqcaselessblk (const_bstring b0, const void * blk, int len) 692 | * 693 | * Compare beginning of string b0 with a block of memory of length len 694 | * without differentiating between case for equality. If the beginning of b0 695 | * differs from the memory block other than in case (or if b0 is too short), 696 | * 0 is returned, if the strings are the same, 1 is returned, if there is an 697 | * error, -1 is returned. '\0' characters are not treated in any special 698 | * way. 699 | */ 700 | int bisstemeqcaselessblk (const_bstring b0, const void * blk, int len) { 701 | int i; 702 | 703 | if (bdata (b0) == NULL || b0->slen < 0 || NULL == blk || len < 0) 704 | return BSTR_ERR; 705 | if (b0->slen < len) return BSTR_OK; 706 | if (b0->data == (const unsigned char *) blk || len == 0) return 1; 707 | 708 | for (i = 0; i < len; i ++) { 709 | if (b0->data[i] != ((const unsigned char *) blk)[i]) { 710 | if (downcase (b0->data[i]) != 711 | downcase (((const unsigned char *) blk)[i])) return 0; 712 | } 713 | } 714 | return 1; 715 | } 716 | 717 | /* 718 | * int bltrimws (bstring b) 719 | * 720 | * Delete whitespace contiguous from the left end of the string. 721 | */ 722 | int bltrimws (bstring b) { 723 | int i, len; 724 | 725 | if (b == NULL || b->data == NULL || b->mlen < b->slen || 726 | b->slen < 0 || b->mlen <= 0) return BSTR_ERR; 727 | 728 | for (len = b->slen, i = 0; i < len; i++) { 729 | if (!wspace (b->data[i])) { 730 | return bdelete (b, 0, i); 731 | } 732 | } 733 | 734 | b->data[0] = (unsigned char) '\0'; 735 | b->slen = 0; 736 | return BSTR_OK; 737 | } 738 | 739 | /* 740 | * int brtrimws (bstring b) 741 | * 742 | * Delete whitespace contiguous from the right end of the string. 743 | */ 744 | int brtrimws (bstring b) { 745 | int i; 746 | 747 | if (b == NULL || b->data == NULL || b->mlen < b->slen || 748 | b->slen < 0 || b->mlen <= 0) return BSTR_ERR; 749 | 750 | for (i = b->slen - 1; i >= 0; i--) { 751 | if (!wspace (b->data[i])) { 752 | if (b->mlen > i) b->data[i+1] = (unsigned char) '\0'; 753 | b->slen = i + 1; 754 | return BSTR_OK; 755 | } 756 | } 757 | 758 | b->data[0] = (unsigned char) '\0'; 759 | b->slen = 0; 760 | return BSTR_OK; 761 | } 762 | 763 | /* 764 | * int btrimws (bstring b) 765 | * 766 | * Delete whitespace contiguous from both ends of the string. 767 | */ 768 | int btrimws (bstring b) { 769 | int i, j; 770 | 771 | if (b == NULL || b->data == NULL || b->mlen < b->slen || 772 | b->slen < 0 || b->mlen <= 0) return BSTR_ERR; 773 | 774 | for (i = b->slen - 1; i >= 0; i--) { 775 | if (!wspace (b->data[i])) { 776 | if (b->mlen > i) b->data[i+1] = (unsigned char) '\0'; 777 | b->slen = i + 1; 778 | for (j = 0; wspace (b->data[j]); j++) {} 779 | return bdelete (b, 0, j); 780 | } 781 | } 782 | 783 | b->data[0] = (unsigned char) '\0'; 784 | b->slen = 0; 785 | return BSTR_OK; 786 | } 787 | 788 | /* int biseq (const_bstring b0, const_bstring b1) 789 | * 790 | * Compare the string b0 and b1. If the strings differ, 0 is returned, if 791 | * the strings are the same, 1 is returned, if there is an error, -1 is 792 | * returned. If the length of the strings are different, this function is 793 | * O(1). '\0' termination characters are not treated in any special way. 794 | */ 795 | int biseq (const_bstring b0, const_bstring b1) { 796 | if (b0 == NULL || b1 == NULL || b0->data == NULL || b1->data == NULL || 797 | b0->slen < 0 || b1->slen < 0) return BSTR_ERR; 798 | if (b0->slen != b1->slen) return BSTR_OK; 799 | if (b0->data == b1->data || b0->slen == 0) return 1; 800 | return !bstr__memcmp (b0->data, b1->data, b0->slen); 801 | } 802 | 803 | /* int bisstemeqblk (const_bstring b0, const void * blk, int len) 804 | * 805 | * Compare beginning of string b0 with a block of memory of length len for 806 | * equality. If the beginning of b0 differs from the memory block (or if b0 807 | * is too short), 0 is returned, if the strings are the same, 1 is returned, 808 | * if there is an error, -1 is returned. '\0' characters are not treated in 809 | * any special way. 810 | */ 811 | int bisstemeqblk (const_bstring b0, const void * blk, int len) { 812 | int i; 813 | 814 | if (bdata (b0) == NULL || b0->slen < 0 || NULL == blk || len < 0) 815 | return BSTR_ERR; 816 | if (b0->slen < len) return BSTR_OK; 817 | if (b0->data == (const unsigned char *) blk || len == 0) return 1; 818 | 819 | for (i = 0; i < len; i ++) { 820 | if (b0->data[i] != ((const unsigned char *) blk)[i]) return BSTR_OK; 821 | } 822 | return 1; 823 | } 824 | 825 | /* int biseqcstr (const_bstring b, const char *s) 826 | * 827 | * Compare the bstring b and char * string s. The C string s must be '\0' 828 | * terminated at exactly the length of the bstring b, and the contents 829 | * between the two must be identical with the bstring b with no '\0' 830 | * characters for the two contents to be considered equal. This is 831 | * equivalent to the condition that their current contents will be always be 832 | * equal when comparing them in the same format after converting one or the 833 | * other. If the strings are equal 1 is returned, if they are unequal 0 is 834 | * returned and if there is a detectable error BSTR_ERR is returned. 835 | */ 836 | int biseqcstr (const_bstring b, const char * s) { 837 | int i; 838 | if (b == NULL || s == NULL || b->data == NULL || b->slen < 0) return BSTR_ERR; 839 | for (i=0; i < b->slen; i++) { 840 | if (s[i] == '\0' || b->data[i] != (unsigned char) s[i]) return BSTR_OK; 841 | } 842 | return s[i] == '\0'; 843 | } 844 | 845 | /* int biseqcstrcaseless (const_bstring b, const char *s) 846 | * 847 | * Compare the bstring b and char * string s. The C string s must be '\0' 848 | * terminated at exactly the length of the bstring b, and the contents 849 | * between the two must be identical except for case with the bstring b with 850 | * no '\0' characters for the two contents to be considered equal. This is 851 | * equivalent to the condition that their current contents will be always be 852 | * equal ignoring case when comparing them in the same format after 853 | * converting one or the other. If the strings are equal, except for case, 854 | * 1 is returned, if they are unequal regardless of case 0 is returned and 855 | * if there is a detectable error BSTR_ERR is returned. 856 | */ 857 | int biseqcstrcaseless (const_bstring b, const char * s) { 858 | int i; 859 | if (b == NULL || s == NULL || b->data == NULL || b->slen < 0) return BSTR_ERR; 860 | for (i=0; i < b->slen; i++) { 861 | if (s[i] == '\0' || 862 | (b->data[i] != (unsigned char) s[i] && 863 | downcase (b->data[i]) != (unsigned char) downcase (s[i]))) 864 | return BSTR_OK; 865 | } 866 | return s[i] == '\0'; 867 | } 868 | 869 | /* int bstrcmp (const_bstring b0, const_bstring b1) 870 | * 871 | * Compare the string b0 and b1. If there is an error, SHRT_MIN is returned, 872 | * otherwise a value less than or greater than zero, indicating that the 873 | * string pointed to by b0 is lexicographically less than or greater than 874 | * the string pointed to by b1 is returned. If the the string lengths are 875 | * unequal but the characters up until the length of the shorter are equal 876 | * then a value less than, or greater than zero, indicating that the string 877 | * pointed to by b0 is shorter or longer than the string pointed to by b1 is 878 | * returned. 0 is returned if and only if the two strings are the same. If 879 | * the length of the strings are different, this function is O(n). Like its 880 | * standard C library counter part strcmp, the comparison does not proceed 881 | * past any '\0' termination characters encountered. 882 | */ 883 | int bstrcmp (const_bstring b0, const_bstring b1) { 884 | int i, v, n; 885 | 886 | if (b0 == NULL || b1 == NULL || b0->data == NULL || b1->data == NULL || 887 | b0->slen < 0 || b1->slen < 0) return SHRT_MIN; 888 | n = b0->slen; if (n > b1->slen) n = b1->slen; 889 | if (b0->slen == b1->slen && (b0->data == b1->data || b0->slen == 0)) 890 | return BSTR_OK; 891 | 892 | for (i = 0; i < n; i ++) { 893 | v = ((char) b0->data[i]) - ((char) b1->data[i]); 894 | if (v != 0) return v; 895 | if (b0->data[i] == (unsigned char) '\0') return BSTR_OK; 896 | } 897 | 898 | if (b0->slen > n) return 1; 899 | if (b1->slen > n) return -1; 900 | return BSTR_OK; 901 | } 902 | 903 | /* int bstrncmp (const_bstring b0, const_bstring b1, int n) 904 | * 905 | * Compare the string b0 and b1 for at most n characters. If there is an 906 | * error, SHRT_MIN is returned, otherwise a value is returned as if b0 and 907 | * b1 were first truncated to at most n characters then bstrcmp was called 908 | * with these new strings are paremeters. If the length of the strings are 909 | * different, this function is O(n). Like its standard C library counter 910 | * part strcmp, the comparison does not proceed past any '\0' termination 911 | * characters encountered. 912 | */ 913 | int bstrncmp (const_bstring b0, const_bstring b1, int n) { 914 | int i, v, m; 915 | 916 | if (b0 == NULL || b1 == NULL || b0->data == NULL || b1->data == NULL || 917 | b0->slen < 0 || b1->slen < 0) return SHRT_MIN; 918 | m = n; 919 | if (m > b0->slen) m = b0->slen; 920 | if (m > b1->slen) m = b1->slen; 921 | 922 | if (b0->data != b1->data) { 923 | for (i = 0; i < m; i ++) { 924 | v = ((char) b0->data[i]) - ((char) b1->data[i]); 925 | if (v != 0) return v; 926 | if (b0->data[i] == (unsigned char) '\0') return BSTR_OK; 927 | } 928 | } 929 | 930 | if (n == m || b0->slen == b1->slen) return BSTR_OK; 931 | 932 | if (b0->slen > m) return 1; 933 | return -1; 934 | } 935 | 936 | /* bstring bmidstr (const_bstring b, int left, int len) 937 | * 938 | * Create a bstring which is the substring of b starting from position left 939 | * and running for a length len (clamped by the end of the bstring b.) If 940 | * b is detectably invalid, then NULL is returned. The section described 941 | * by (left, len) is clamped to the boundaries of b. 942 | */ 943 | bstring bmidstr (const_bstring b, int left, int len) { 944 | 945 | if (b == NULL || b->slen < 0 || b->data == NULL) return NULL; 946 | 947 | if (left < 0) { 948 | len += left; 949 | left = 0; 950 | } 951 | 952 | if (len > b->slen - left) len = b->slen - left; 953 | 954 | if (len <= 0) return bfromcstr (""); 955 | return blk2bstr (b->data + left, len); 956 | } 957 | 958 | /* int bdelete (bstring b, int pos, int len) 959 | * 960 | * Removes characters from pos to pos+len-1 inclusive and shifts the tail of 961 | * the bstring starting from pos+len to pos. len must be positive for this 962 | * call to have any effect. The section of the string described by (pos, 963 | * len) is clamped to boundaries of the bstring b. 964 | */ 965 | int bdelete (bstring b, int pos, int len) { 966 | /* Clamp to left side of bstring */ 967 | if (pos < 0) { 968 | len += pos; 969 | pos = 0; 970 | } 971 | 972 | if (len < 0 || b == NULL || b->data == NULL || b->slen < 0 || 973 | b->mlen < b->slen || b->mlen <= 0) 974 | return BSTR_ERR; 975 | if (len > 0 && pos < b->slen) { 976 | if (pos + len >= b->slen) { 977 | b->slen = pos; 978 | } else { 979 | bBlockCopy ((char *) (b->data + pos), 980 | (char *) (b->data + pos + len), 981 | b->slen - (pos+len)); 982 | b->slen -= len; 983 | } 984 | b->data[b->slen] = (unsigned char) '\0'; 985 | } 986 | return BSTR_OK; 987 | } 988 | 989 | /* int bdestroy (bstring b) 990 | * 991 | * Free up the bstring. Note that if b is detectably invalid or not writable 992 | * then no action is performed and BSTR_ERR is returned. Like a freed memory 993 | * allocation, dereferences, writes or any other action on b after it has 994 | * been bdestroyed is undefined. 995 | */ 996 | int bdestroy (bstring b) { 997 | if (b == NULL || b->slen < 0 || b->mlen <= 0 || b->mlen < b->slen || 998 | b->data == NULL) 999 | return BSTR_ERR; 1000 | 1001 | bstr__free (b->data); 1002 | 1003 | /* In case there is any stale usage, there is one more chance to 1004 | notice this error. */ 1005 | 1006 | b->slen = -1; 1007 | b->mlen = -__LINE__; 1008 | b->data = NULL; 1009 | 1010 | bstr__free (b); 1011 | return BSTR_OK; 1012 | } 1013 | 1014 | /* int binstr (const_bstring b1, int pos, const_bstring b2) 1015 | * 1016 | * Search for the bstring b2 in b1 starting from position pos, and searching 1017 | * forward. If it is found then return with the first position where it is 1018 | * found, otherwise return BSTR_ERR. Note that this is just a brute force 1019 | * string searcher that does not attempt clever things like the Boyer-Moore 1020 | * search algorithm. Because of this there are many degenerate cases where 1021 | * this can take much longer than it needs to. 1022 | */ 1023 | int binstr (const_bstring b1, int pos, const_bstring b2) { 1024 | int j, ii, ll, lf; 1025 | unsigned char * d0; 1026 | unsigned char c0; 1027 | register unsigned char * d1; 1028 | register unsigned char c1; 1029 | register int i; 1030 | 1031 | if (b1 == NULL || b1->data == NULL || b1->slen < 0 || 1032 | b2 == NULL || b2->data == NULL || b2->slen < 0) return BSTR_ERR; 1033 | if (b1->slen == pos) return (b2->slen == 0)?pos:BSTR_ERR; 1034 | if (b1->slen < pos || pos < 0) return BSTR_ERR; 1035 | if (b2->slen == 0) return pos; 1036 | 1037 | /* No space to find such a string? */ 1038 | if ((lf = b1->slen - b2->slen + 1) <= pos) return BSTR_ERR; 1039 | 1040 | /* An obvious alias case */ 1041 | if (b1->data == b2->data && pos == 0) return 0; 1042 | 1043 | i = pos; 1044 | 1045 | d0 = b2->data; 1046 | d1 = b1->data; 1047 | ll = b2->slen; 1048 | 1049 | /* Peel off the b2->slen == 1 case */ 1050 | c0 = d0[0]; 1051 | if (1 == ll) { 1052 | for (;i < lf; i++) if (c0 == d1[i]) return i; 1053 | return BSTR_ERR; 1054 | } 1055 | 1056 | c1 = c0; 1057 | j = 0; 1058 | lf = b1->slen - 1; 1059 | 1060 | ii = -1; 1061 | if (i < lf) do { 1062 | /* Unrolled current character test */ 1063 | if (c1 != d1[i]) { 1064 | if (c1 != d1[1+i]) { 1065 | i += 2; 1066 | continue; 1067 | } 1068 | i++; 1069 | } 1070 | 1071 | /* Take note if this is the start of a potential match */ 1072 | if (0 == j) ii = i; 1073 | 1074 | /* Shift the test character down by one */ 1075 | j++; 1076 | i++; 1077 | 1078 | /* If this isn't past the last character continue */ 1079 | if (j < ll) { 1080 | c1 = d0[j]; 1081 | continue; 1082 | } 1083 | 1084 | N0:; 1085 | 1086 | /* If no characters mismatched, then we matched */ 1087 | if (i == ii+j) return ii; 1088 | 1089 | /* Shift back to the beginning */ 1090 | i -= j; 1091 | j = 0; 1092 | c1 = c0; 1093 | } while (i < lf); 1094 | 1095 | /* Deal with last case if unrolling caused a misalignment */ 1096 | if (i == lf && ll == j+1 && c1 == d1[i]) goto N0; 1097 | 1098 | return BSTR_ERR; 1099 | } 1100 | 1101 | /* int binstrr (const_bstring b1, int pos, const_bstring b2) 1102 | * 1103 | * Search for the bstring b2 in b1 starting from position pos, and searching 1104 | * backward. If it is found then return with the first position where it is 1105 | * found, otherwise return BSTR_ERR. Note that this is just a brute force 1106 | * string searcher that does not attempt clever things like the Boyer-Moore 1107 | * search algorithm. Because of this there are many degenerate cases where 1108 | * this can take much longer than it needs to. 1109 | */ 1110 | int binstrr (const_bstring b1, int pos, const_bstring b2) { 1111 | int j, i, l; 1112 | unsigned char * d0, * d1; 1113 | 1114 | if (b1 == NULL || b1->data == NULL || b1->slen < 0 || 1115 | b2 == NULL || b2->data == NULL || b2->slen < 0) return BSTR_ERR; 1116 | if (b1->slen == pos && b2->slen == 0) return pos; 1117 | if (b1->slen < pos || pos < 0) return BSTR_ERR; 1118 | if (b2->slen == 0) return pos; 1119 | 1120 | /* Obvious alias case */ 1121 | if (b1->data == b2->data && pos == 0 && b2->slen <= b1->slen) return 0; 1122 | 1123 | i = pos; 1124 | if ((l = b1->slen - b2->slen) < 0) return BSTR_ERR; 1125 | 1126 | /* If no space to find such a string then snap back */ 1127 | if (l + 1 <= i) i = l; 1128 | j = 0; 1129 | 1130 | d0 = b2->data; 1131 | d1 = b1->data; 1132 | l = b2->slen; 1133 | 1134 | for (;;) { 1135 | if (d0[j] == d1[i + j]) { 1136 | j ++; 1137 | if (j >= l) return i; 1138 | } else { 1139 | i --; 1140 | if (i < 0) break; 1141 | j=0; 1142 | } 1143 | } 1144 | 1145 | return BSTR_ERR; 1146 | } 1147 | 1148 | /* int binstrcaseless (const_bstring b1, int pos, const_bstring b2) 1149 | * 1150 | * Search for the bstring b2 in b1 starting from position pos, and searching 1151 | * forward but without regard to case. If it is found then return with the 1152 | * first position where it is found, otherwise return BSTR_ERR. Note that 1153 | * this is just a brute force string searcher that does not attempt clever 1154 | * things like the Boyer-Moore search algorithm. Because of this there are 1155 | * many degenerate cases where this can take much longer than it needs to. 1156 | */ 1157 | int binstrcaseless (const_bstring b1, int pos, const_bstring b2) { 1158 | int j, i, l, ll; 1159 | unsigned char * d0, * d1; 1160 | 1161 | if (b1 == NULL || b1->data == NULL || b1->slen < 0 || 1162 | b2 == NULL || b2->data == NULL || b2->slen < 0) return BSTR_ERR; 1163 | if (b1->slen == pos) return (b2->slen == 0)?pos:BSTR_ERR; 1164 | if (b1->slen < pos || pos < 0) return BSTR_ERR; 1165 | if (b2->slen == 0) return pos; 1166 | 1167 | l = b1->slen - b2->slen + 1; 1168 | 1169 | /* No space to find such a string? */ 1170 | if (l <= pos) return BSTR_ERR; 1171 | 1172 | /* An obvious alias case */ 1173 | if (b1->data == b2->data && pos == 0) return BSTR_OK; 1174 | 1175 | i = pos; 1176 | j = 0; 1177 | 1178 | d0 = b2->data; 1179 | d1 = b1->data; 1180 | ll = b2->slen; 1181 | 1182 | for (;;) { 1183 | if (d0[j] == d1[i + j] || downcase (d0[j]) == downcase (d1[i + j])) { 1184 | j ++; 1185 | if (j >= ll) return i; 1186 | } else { 1187 | i ++; 1188 | if (i >= l) break; 1189 | j=0; 1190 | } 1191 | } 1192 | 1193 | return BSTR_ERR; 1194 | } 1195 | 1196 | /* int binstrrcaseless (const_bstring b1, int pos, const_bstring b2) 1197 | * 1198 | * Search for the bstring b2 in b1 starting from position pos, and searching 1199 | * backward but without regard to case. If it is found then return with the 1200 | * first position where it is found, otherwise return BSTR_ERR. Note that 1201 | * this is just a brute force string searcher that does not attempt clever 1202 | * things like the Boyer-Moore search algorithm. Because of this there are 1203 | * many degenerate cases where this can take much longer than it needs to. 1204 | */ 1205 | int binstrrcaseless (const_bstring b1, int pos, const_bstring b2) { 1206 | int j, i, l; 1207 | unsigned char * d0, * d1; 1208 | 1209 | if (b1 == NULL || b1->data == NULL || b1->slen < 0 || 1210 | b2 == NULL || b2->data == NULL || b2->slen < 0) return BSTR_ERR; 1211 | if (b1->slen == pos && b2->slen == 0) return pos; 1212 | if (b1->slen < pos || pos < 0) return BSTR_ERR; 1213 | if (b2->slen == 0) return pos; 1214 | 1215 | /* Obvious alias case */ 1216 | if (b1->data == b2->data && pos == 0 && b2->slen <= b1->slen) return BSTR_OK; 1217 | 1218 | i = pos; 1219 | if ((l = b1->slen - b2->slen) < 0) return BSTR_ERR; 1220 | 1221 | /* If no space to find such a string then snap back */ 1222 | if (l + 1 <= i) i = l; 1223 | j = 0; 1224 | 1225 | d0 = b2->data; 1226 | d1 = b1->data; 1227 | l = b2->slen; 1228 | 1229 | for (;;) { 1230 | if (d0[j] == d1[i + j] || downcase (d0[j]) == downcase (d1[i + j])) { 1231 | j ++; 1232 | if (j >= l) return i; 1233 | } else { 1234 | i --; 1235 | if (i < 0) break; 1236 | j=0; 1237 | } 1238 | } 1239 | 1240 | return BSTR_ERR; 1241 | } 1242 | 1243 | 1244 | /* int bstrchrp (const_bstring b, int c, int pos) 1245 | * 1246 | * Search for the character c in b forwards from the position pos 1247 | * (inclusive). 1248 | */ 1249 | int bstrchrp (const_bstring b, int c, int pos) { 1250 | unsigned char * p; 1251 | 1252 | if (b == NULL || b->data == NULL || b->slen <= pos || pos < 0) return BSTR_ERR; 1253 | p = (unsigned char *) bstr__memchr ((b->data + pos), (unsigned char) c, (b->slen - pos)); 1254 | if (p) return (int) (p - b->data); 1255 | return BSTR_ERR; 1256 | } 1257 | 1258 | /* int bstrrchrp (const_bstring b, int c, int pos) 1259 | * 1260 | * Search for the character c in b backwards from the position pos in string 1261 | * (inclusive). 1262 | */ 1263 | int bstrrchrp (const_bstring b, int c, int pos) { 1264 | int i; 1265 | 1266 | if (b == NULL || b->data == NULL || b->slen <= pos || pos < 0) return BSTR_ERR; 1267 | for (i=pos; i >= 0; i--) { 1268 | if (b->data[i] == (unsigned char) c) return i; 1269 | } 1270 | return BSTR_ERR; 1271 | } 1272 | 1273 | #if !defined (BSTRLIB_AGGRESSIVE_MEMORY_FOR_SPEED_TRADEOFF) 1274 | #define LONG_LOG_BITS_QTY (3) 1275 | #define LONG_BITS_QTY (1 << LONG_LOG_BITS_QTY) 1276 | #define LONG_TYPE unsigned char 1277 | 1278 | #define CFCLEN ((1 << CHAR_BIT) / LONG_BITS_QTY) 1279 | struct charField { LONG_TYPE content[CFCLEN]; }; 1280 | #define testInCharField(cf,c) ((cf)->content[(c) >> LONG_LOG_BITS_QTY] & (((long)1) << ((c) & (LONG_BITS_QTY-1)))) 1281 | #define setInCharField(cf,idx) { \ 1282 | unsigned int c = (unsigned int) (idx); \ 1283 | (cf)->content[c >> LONG_LOG_BITS_QTY] |= (LONG_TYPE) (1ul << (c & (LONG_BITS_QTY-1))); \ 1284 | } 1285 | 1286 | #else 1287 | 1288 | #define CFCLEN (1 << CHAR_BIT) 1289 | struct charField { unsigned char content[CFCLEN]; }; 1290 | #define testInCharField(cf,c) ((cf)->content[(unsigned char) (c)]) 1291 | #define setInCharField(cf,idx) (cf)->content[(unsigned int) (idx)] = ~0 1292 | 1293 | #endif 1294 | 1295 | /* Convert a bstring to charField */ 1296 | static int buildCharField (struct charField * cf, const_bstring b) { 1297 | int i; 1298 | if (b == NULL || b->data == NULL || b->slen <= 0) return BSTR_ERR; 1299 | memset ((void *) cf->content, 0, sizeof (struct charField)); 1300 | for (i=0; i < b->slen; i++) { 1301 | setInCharField (cf, b->data[i]); 1302 | } 1303 | return BSTR_OK; 1304 | } 1305 | 1306 | static void invertCharField (struct charField * cf) { 1307 | int i; 1308 | for (i=0; i < CFCLEN; i++) cf->content[i] = ~cf->content[i]; 1309 | } 1310 | 1311 | /* Inner engine for binchr */ 1312 | static int binchrCF (const unsigned char * data, int len, int pos, const struct charField * cf) { 1313 | int i; 1314 | for (i=pos; i < len; i++) { 1315 | unsigned char c = (unsigned char) data[i]; 1316 | if (testInCharField (cf, c)) return i; 1317 | } 1318 | return BSTR_ERR; 1319 | } 1320 | 1321 | /* int binchr (const_bstring b0, int pos, const_bstring b1); 1322 | * 1323 | * Search for the first position in b0 starting from pos or after, in which 1324 | * one of the characters in b1 is found and return it. If such a position 1325 | * does not exist in b0, then BSTR_ERR is returned. 1326 | */ 1327 | int binchr (const_bstring b0, int pos, const_bstring b1) { 1328 | struct charField chrs; 1329 | if (pos < 0 || b0 == NULL || b0->data == NULL || 1330 | b0->slen <= pos) return BSTR_ERR; 1331 | if (1 == b1->slen) return bstrchrp (b0, b1->data[0], pos); 1332 | if (0 > buildCharField (&chrs, b1)) return BSTR_ERR; 1333 | return binchrCF (b0->data, b0->slen, pos, &chrs); 1334 | } 1335 | 1336 | /* Inner engine for binchrr */ 1337 | static int binchrrCF (const unsigned char * data, int pos, const struct charField * cf) { 1338 | int i; 1339 | for (i=pos; i >= 0; i--) { 1340 | unsigned int c = (unsigned int) data[i]; 1341 | if (testInCharField (cf, c)) return i; 1342 | } 1343 | return BSTR_ERR; 1344 | } 1345 | 1346 | /* int binchrr (const_bstring b0, int pos, const_bstring b1); 1347 | * 1348 | * Search for the last position in b0 no greater than pos, in which one of 1349 | * the characters in b1 is found and return it. If such a position does not 1350 | * exist in b0, then BSTR_ERR is returned. 1351 | */ 1352 | int binchrr (const_bstring b0, int pos, const_bstring b1) { 1353 | struct charField chrs; 1354 | if (pos < 0 || b0 == NULL || b0->data == NULL || b1 == NULL || 1355 | b0->slen < pos) return BSTR_ERR; 1356 | if (pos == b0->slen) pos--; 1357 | if (1 == b1->slen) return bstrrchrp (b0, b1->data[0], pos); 1358 | if (0 > buildCharField (&chrs, b1)) return BSTR_ERR; 1359 | return binchrrCF (b0->data, pos, &chrs); 1360 | } 1361 | 1362 | /* int bninchr (const_bstring b0, int pos, const_bstring b1); 1363 | * 1364 | * Search for the first position in b0 starting from pos or after, in which 1365 | * none of the characters in b1 is found and return it. If such a position 1366 | * does not exist in b0, then BSTR_ERR is returned. 1367 | */ 1368 | int bninchr (const_bstring b0, int pos, const_bstring b1) { 1369 | struct charField chrs; 1370 | if (pos < 0 || b0 == NULL || b0->data == NULL || 1371 | b0->slen <= pos) return BSTR_ERR; 1372 | if (buildCharField (&chrs, b1) < 0) return BSTR_ERR; 1373 | invertCharField (&chrs); 1374 | return binchrCF (b0->data, b0->slen, pos, &chrs); 1375 | } 1376 | 1377 | /* int bninchrr (const_bstring b0, int pos, const_bstring b1); 1378 | * 1379 | * Search for the last position in b0 no greater than pos, in which none of 1380 | * the characters in b1 is found and return it. If such a position does not 1381 | * exist in b0, then BSTR_ERR is returned. 1382 | */ 1383 | int bninchrr (const_bstring b0, int pos, const_bstring b1) { 1384 | struct charField chrs; 1385 | if (pos < 0 || b0 == NULL || b0->data == NULL || 1386 | b0->slen < pos) return BSTR_ERR; 1387 | if (pos == b0->slen) pos--; 1388 | if (buildCharField (&chrs, b1) < 0) return BSTR_ERR; 1389 | invertCharField (&chrs); 1390 | return binchrrCF (b0->data, pos, &chrs); 1391 | } 1392 | 1393 | /* int bsetstr (bstring b0, int pos, bstring b1, unsigned char fill) 1394 | * 1395 | * Overwrite the string b0 starting at position pos with the string b1. If 1396 | * the position pos is past the end of b0, then the character "fill" is 1397 | * appended as necessary to make up the gap between the end of b0 and pos. 1398 | * If b1 is NULL, it behaves as if it were a 0-length string. 1399 | */ 1400 | int bsetstr (bstring b0, int pos, const_bstring b1, unsigned char fill) { 1401 | int d, newlen; 1402 | ptrdiff_t pd; 1403 | bstring aux = (bstring) b1; 1404 | 1405 | if (pos < 0 || b0 == NULL || b0->slen < 0 || NULL == b0->data || 1406 | b0->mlen < b0->slen || b0->mlen <= 0) return BSTR_ERR; 1407 | if (b1 != NULL && (b1->slen < 0 || b1->data == NULL)) return BSTR_ERR; 1408 | 1409 | d = pos; 1410 | 1411 | /* Aliasing case */ 1412 | if (NULL != aux) { 1413 | if ((pd = (ptrdiff_t) (b1->data - b0->data)) >= 0 && pd < (ptrdiff_t) b0->mlen) { 1414 | if (NULL == (aux = bstrcpy (b1))) return BSTR_ERR; 1415 | } 1416 | d += aux->slen; 1417 | } 1418 | 1419 | /* Increase memory size if necessary */ 1420 | if (balloc (b0, d + 1) != BSTR_OK) { 1421 | if (aux != b1) bdestroy (aux); 1422 | return BSTR_ERR; 1423 | } 1424 | 1425 | newlen = b0->slen; 1426 | 1427 | /* Fill in "fill" character as necessary */ 1428 | if (pos > newlen) { 1429 | bstr__memset (b0->data + b0->slen, (int) fill, (size_t) (pos - b0->slen)); 1430 | newlen = pos; 1431 | } 1432 | 1433 | /* Copy b1 to position pos in b0. */ 1434 | if (aux != NULL) { 1435 | bBlockCopy ((char *) (b0->data + pos), (char *) aux->data, aux->slen); 1436 | if (aux != b1) bdestroy (aux); 1437 | } 1438 | 1439 | /* Indicate the potentially increased size of b0 */ 1440 | if (d > newlen) newlen = d; 1441 | 1442 | b0->slen = newlen; 1443 | b0->data[newlen] = (unsigned char) '\0'; 1444 | 1445 | return BSTR_OK; 1446 | } 1447 | 1448 | /* int binsert (bstring b1, int pos, bstring b2, unsigned char fill) 1449 | * 1450 | * Inserts the string b2 into b1 at position pos. If the position pos is 1451 | * past the end of b1, then the character "fill" is appended as necessary to 1452 | * make up the gap between the end of b1 and pos. Unlike bsetstr, binsert 1453 | * does not allow b2 to be NULL. 1454 | */ 1455 | int binsert (bstring b1, int pos, const_bstring b2, unsigned char fill) { 1456 | int d, l; 1457 | ptrdiff_t pd; 1458 | bstring aux = (bstring) b2; 1459 | 1460 | if (pos < 0 || b1 == NULL || b2 == NULL || b1->slen < 0 || 1461 | b2->slen < 0 || b1->mlen < b1->slen || b1->mlen <= 0) return BSTR_ERR; 1462 | 1463 | /* Aliasing case */ 1464 | if ((pd = (ptrdiff_t) (b2->data - b1->data)) >= 0 && pd < (ptrdiff_t) b1->mlen) { 1465 | if (NULL == (aux = bstrcpy (b2))) return BSTR_ERR; 1466 | } 1467 | 1468 | /* Compute the two possible end pointers */ 1469 | d = b1->slen + aux->slen; 1470 | l = pos + aux->slen; 1471 | if ((d|l) < 0) return BSTR_ERR; 1472 | 1473 | if (l > d) { 1474 | /* Inserting past the end of the string */ 1475 | if (balloc (b1, l + 1) != BSTR_OK) { 1476 | if (aux != b2) bdestroy (aux); 1477 | return BSTR_ERR; 1478 | } 1479 | bstr__memset (b1->data + b1->slen, (int) fill, (size_t) (pos - b1->slen)); 1480 | b1->slen = l; 1481 | } else { 1482 | /* Inserting in the middle of the string */ 1483 | if (balloc (b1, d + 1) != BSTR_OK) { 1484 | if (aux != b2) bdestroy (aux); 1485 | return BSTR_ERR; 1486 | } 1487 | bBlockCopy (b1->data + l, b1->data + pos, d - l); 1488 | b1->slen = d; 1489 | } 1490 | bBlockCopy (b1->data + pos, aux->data, aux->slen); 1491 | b1->data[b1->slen] = (unsigned char) '\0'; 1492 | if (aux != b2) bdestroy (aux); 1493 | return BSTR_OK; 1494 | } 1495 | 1496 | /* int breplace (bstring b1, int pos, int len, bstring b2, 1497 | * unsigned char fill) 1498 | * 1499 | * Replace a section of a string from pos for a length len with the string b2. 1500 | * fill is used is pos > b1->slen. 1501 | */ 1502 | int breplace (bstring b1, int pos, int len, const_bstring b2, 1503 | unsigned char fill) { 1504 | int pl, ret; 1505 | ptrdiff_t pd; 1506 | bstring aux = (bstring) b2; 1507 | 1508 | if (pos < 0 || len < 0 || (pl = pos + len) < 0 || b1 == NULL || 1509 | b2 == NULL || b1->data == NULL || b2->data == NULL || 1510 | b1->slen < 0 || b2->slen < 0 || b1->mlen < b1->slen || 1511 | b1->mlen <= 0) return BSTR_ERR; 1512 | 1513 | /* Straddles the end? */ 1514 | if (pl >= b1->slen) { 1515 | if ((ret = bsetstr (b1, pos, b2, fill)) < 0) return ret; 1516 | if (pos + b2->slen < b1->slen) { 1517 | b1->slen = pos + b2->slen; 1518 | b1->data[b1->slen] = (unsigned char) '\0'; 1519 | } 1520 | return ret; 1521 | } 1522 | 1523 | /* Aliasing case */ 1524 | if ((pd = (ptrdiff_t) (b2->data - b1->data)) >= 0 && pd < (ptrdiff_t) b1->slen) { 1525 | if (NULL == (aux = bstrcpy (b2))) return BSTR_ERR; 1526 | } 1527 | 1528 | if (aux->slen > len) { 1529 | if (balloc (b1, b1->slen + aux->slen - len) != BSTR_OK) { 1530 | if (aux != b2) bdestroy (aux); 1531 | return BSTR_ERR; 1532 | } 1533 | } 1534 | 1535 | if (aux->slen != len) bstr__memmove (b1->data + pos + aux->slen, b1->data + pos + len, b1->slen - (pos + len)); 1536 | bstr__memcpy (b1->data + pos, aux->data, aux->slen); 1537 | b1->slen += aux->slen - len; 1538 | b1->data[b1->slen] = (unsigned char) '\0'; 1539 | if (aux != b2) bdestroy (aux); 1540 | return BSTR_OK; 1541 | } 1542 | 1543 | /* 1544 | * findreplaceengine is used to implement bfindreplace and 1545 | * bfindreplacecaseless. It works by breaking the three cases of 1546 | * expansion, reduction and replacement, and solving each of these 1547 | * in the most efficient way possible. 1548 | */ 1549 | 1550 | typedef int (*instr_fnptr) (const_bstring s1, int pos, const_bstring s2); 1551 | 1552 | #define INITIAL_STATIC_FIND_INDEX_COUNT 32 1553 | 1554 | static int findreplaceengine (bstring b, const_bstring find, const_bstring repl, int pos, instr_fnptr instr) { 1555 | int i, ret, slen, mlen, delta, acc; 1556 | int * d; 1557 | int static_d[INITIAL_STATIC_FIND_INDEX_COUNT+1]; /* This +1 is unnecessary, but it shuts up LINT. */ 1558 | ptrdiff_t pd; 1559 | bstring auxf = (bstring) find; 1560 | bstring auxr = (bstring) repl; 1561 | 1562 | if (b == NULL || b->data == NULL || find == NULL || 1563 | find->data == NULL || repl == NULL || repl->data == NULL || 1564 | pos < 0 || find->slen <= 0 || b->mlen < 0 || b->slen > b->mlen || 1565 | b->mlen <= 0 || b->slen < 0 || repl->slen < 0) return BSTR_ERR; 1566 | if (pos > b->slen - find->slen) return BSTR_OK; 1567 | 1568 | /* Alias with find string */ 1569 | pd = (ptrdiff_t) (find->data - b->data); 1570 | if ((ptrdiff_t) (pos - find->slen) < pd && pd < (ptrdiff_t) b->slen) { 1571 | if (NULL == (auxf = bstrcpy (find))) return BSTR_ERR; 1572 | } 1573 | 1574 | /* Alias with repl string */ 1575 | pd = (ptrdiff_t) (repl->data - b->data); 1576 | if ((ptrdiff_t) (pos - repl->slen) < pd && pd < (ptrdiff_t) b->slen) { 1577 | if (NULL == (auxr = bstrcpy (repl))) { 1578 | if (auxf != find) bdestroy (auxf); 1579 | return BSTR_ERR; 1580 | } 1581 | } 1582 | 1583 | delta = auxf->slen - auxr->slen; 1584 | 1585 | /* in-place replacement since find and replace strings are of equal 1586 | length */ 1587 | if (delta == 0) { 1588 | while ((pos = instr (b, pos, auxf)) >= 0) { 1589 | bstr__memcpy (b->data + pos, auxr->data, auxr->slen); 1590 | pos += auxf->slen; 1591 | } 1592 | if (auxf != find) bdestroy (auxf); 1593 | if (auxr != repl) bdestroy (auxr); 1594 | return BSTR_OK; 1595 | } 1596 | 1597 | /* shrinking replacement since auxf->slen > auxr->slen */ 1598 | if (delta > 0) { 1599 | acc = 0; 1600 | 1601 | while ((i = instr (b, pos, auxf)) >= 0) { 1602 | if (acc && i > pos) 1603 | bstr__memmove (b->data + pos - acc, b->data + pos, i - pos); 1604 | if (auxr->slen) 1605 | bstr__memcpy (b->data + i - acc, auxr->data, auxr->slen); 1606 | acc += delta; 1607 | pos = i + auxf->slen; 1608 | } 1609 | 1610 | if (acc) { 1611 | i = b->slen; 1612 | if (i > pos) 1613 | bstr__memmove (b->data + pos - acc, b->data + pos, i - pos); 1614 | b->slen -= acc; 1615 | b->data[b->slen] = (unsigned char) '\0'; 1616 | } 1617 | 1618 | if (auxf != find) bdestroy (auxf); 1619 | if (auxr != repl) bdestroy (auxr); 1620 | return BSTR_OK; 1621 | } 1622 | 1623 | /* expanding replacement since find->slen < repl->slen. Its a lot 1624 | more complicated. This works by first finding all the matches and 1625 | storing them to a growable array, then doing at most one resize of 1626 | the destination bstring and then performing the direct memory transfers 1627 | of the string segment pieces to form the final result. The growable 1628 | array of matches uses a deferred doubling reallocing strategy. What 1629 | this means is that it starts as a reasonably fixed sized auto array in 1630 | the hopes that many if not most cases will never need to grow this 1631 | array. But it switches as soon as the bounds of the array will be 1632 | exceeded. An extra find result is always appended to this array that 1633 | corresponds to the end of the destination string, so slen is checked 1634 | against mlen - 1 rather than mlen before resizing. 1635 | */ 1636 | 1637 | mlen = INITIAL_STATIC_FIND_INDEX_COUNT; 1638 | d = (int *) static_d; /* Avoid malloc for trivial/initial cases */ 1639 | acc = slen = 0; 1640 | 1641 | while ((pos = instr (b, pos, auxf)) >= 0) { 1642 | if (slen >= mlen - 1) { 1643 | int sl, *t; 1644 | 1645 | mlen += mlen; 1646 | sl = sizeof (int *) * mlen; 1647 | if (static_d == d) d = NULL; /* static_d cannot be realloced */ 1648 | if (mlen <= 0 || sl < mlen || NULL == (t = (int *) bstr__realloc (d, sl))) { 1649 | ret = BSTR_ERR; 1650 | goto done; 1651 | } 1652 | if (NULL == d) bstr__memcpy (t, static_d, sizeof (static_d)); 1653 | d = t; 1654 | } 1655 | d[slen] = pos; 1656 | slen++; 1657 | acc -= delta; 1658 | pos += auxf->slen; 1659 | if (pos < 0 || acc < 0) { 1660 | ret = BSTR_ERR; 1661 | goto done; 1662 | } 1663 | } 1664 | 1665 | /* slen <= INITIAL_STATIC_INDEX_COUNT-1 or mlen-1 here. */ 1666 | d[slen] = b->slen; 1667 | 1668 | if (BSTR_OK == (ret = balloc (b, b->slen + acc + 1))) { 1669 | b->slen += acc; 1670 | for (i = slen-1; i >= 0; i--) { 1671 | int s, l; 1672 | s = d[i] + auxf->slen; 1673 | l = d[i+1] - s; /* d[slen] may be accessed here. */ 1674 | if (l) { 1675 | bstr__memmove (b->data + s + acc, b->data + s, l); 1676 | } 1677 | if (auxr->slen) { 1678 | bstr__memmove (b->data + s + acc - auxr->slen, 1679 | auxr->data, auxr->slen); 1680 | } 1681 | acc += delta; 1682 | } 1683 | b->data[b->slen] = (unsigned char) '\0'; 1684 | } 1685 | 1686 | done:; 1687 | if (static_d == d) d = NULL; 1688 | bstr__free (d); 1689 | if (auxf != find) bdestroy (auxf); 1690 | if (auxr != repl) bdestroy (auxr); 1691 | return ret; 1692 | } 1693 | 1694 | /* int bfindreplace (bstring b, const_bstring find, const_bstring repl, 1695 | * int pos) 1696 | * 1697 | * Replace all occurrences of a find string with a replace string after a 1698 | * given point in a bstring. 1699 | */ 1700 | int bfindreplace (bstring b, const_bstring find, const_bstring repl, int pos) { 1701 | return findreplaceengine (b, find, repl, pos, binstr); 1702 | } 1703 | 1704 | /* int bfindreplacecaseless (bstring b, const_bstring find, const_bstring repl, 1705 | * int pos) 1706 | * 1707 | * Replace all occurrences of a find string, ignoring case, with a replace 1708 | * string after a given point in a bstring. 1709 | */ 1710 | int bfindreplacecaseless (bstring b, const_bstring find, const_bstring repl, int pos) { 1711 | return findreplaceengine (b, find, repl, pos, binstrcaseless); 1712 | } 1713 | 1714 | /* int binsertch (bstring b, int pos, int len, unsigned char fill) 1715 | * 1716 | * Inserts the character fill repeatedly into b at position pos for a 1717 | * length len. If the position pos is past the end of b, then the 1718 | * character "fill" is appended as necessary to make up the gap between the 1719 | * end of b and the position pos + len. 1720 | */ 1721 | int binsertch (bstring b, int pos, int len, unsigned char fill) { 1722 | int d, l, i; 1723 | 1724 | if (pos < 0 || b == NULL || b->slen < 0 || b->mlen < b->slen || 1725 | b->mlen <= 0 || len < 0) return BSTR_ERR; 1726 | 1727 | /* Compute the two possible end pointers */ 1728 | d = b->slen + len; 1729 | l = pos + len; 1730 | if ((d|l) < 0) return BSTR_ERR; 1731 | 1732 | if (l > d) { 1733 | /* Inserting past the end of the string */ 1734 | if (balloc (b, l + 1) != BSTR_OK) return BSTR_ERR; 1735 | pos = b->slen; 1736 | b->slen = l; 1737 | } else { 1738 | /* Inserting in the middle of the string */ 1739 | if (balloc (b, d + 1) != BSTR_OK) return BSTR_ERR; 1740 | for (i = d - 1; i >= l; i--) { 1741 | b->data[i] = b->data[i - len]; 1742 | } 1743 | b->slen = d; 1744 | } 1745 | 1746 | for (i=pos; i < l; i++) b->data[i] = fill; 1747 | b->data[b->slen] = (unsigned char) '\0'; 1748 | return BSTR_OK; 1749 | } 1750 | 1751 | /* int bpattern (bstring b, int len) 1752 | * 1753 | * Replicate the bstring, b in place, end to end repeatedly until it 1754 | * surpasses len characters, then chop the result to exactly len characters. 1755 | * This function operates in-place. The function will return with BSTR_ERR 1756 | * if b is NULL or of length 0, otherwise BSTR_OK is returned. 1757 | */ 1758 | int bpattern (bstring b, int len) { 1759 | int i, d; 1760 | 1761 | d = blength (b); 1762 | if (d <= 0 || len < 0 || balloc (b, len + 1) != BSTR_OK) return BSTR_ERR; 1763 | if (len > 0) { 1764 | if (d == 1) return bsetstr (b, len, NULL, b->data[0]); 1765 | for (i = d; i < len; i++) b->data[i] = b->data[i - d]; 1766 | } 1767 | b->data[len] = (unsigned char) '\0'; 1768 | b->slen = len; 1769 | return BSTR_OK; 1770 | } 1771 | 1772 | #define BS_BUFF_SZ (1024) 1773 | 1774 | /* int breada (bstring b, bNread readPtr, void * parm) 1775 | * 1776 | * Use a finite buffer fread-like function readPtr to concatenate to the 1777 | * bstring b the entire contents of file-like source data in a roughly 1778 | * efficient way. 1779 | */ 1780 | int breada (bstring b, bNread readPtr, void * parm) { 1781 | int i, l, n; 1782 | 1783 | if (b == NULL || b->mlen <= 0 || b->slen < 0 || b->mlen < b->slen || 1784 | b->mlen <= 0 || readPtr == NULL) return BSTR_ERR; 1785 | 1786 | i = b->slen; 1787 | for (n=i+16; ; n += ((n < BS_BUFF_SZ) ? n : BS_BUFF_SZ)) { 1788 | if (BSTR_OK != balloc (b, n + 1)) return BSTR_ERR; 1789 | l = (int) readPtr ((void *) (b->data + i), 1, n - i, parm); 1790 | i += l; 1791 | b->slen = i; 1792 | if (i < n) break; 1793 | } 1794 | 1795 | b->data[i] = (unsigned char) '\0'; 1796 | return BSTR_OK; 1797 | } 1798 | 1799 | /* bstring bread (bNread readPtr, void * parm) 1800 | * 1801 | * Use a finite buffer fread-like function readPtr to create a bstring 1802 | * filled with the entire contents of file-like source data in a roughly 1803 | * efficient way. 1804 | */ 1805 | bstring bread (bNread readPtr, void * parm) { 1806 | bstring buff; 1807 | 1808 | if (0 > breada (buff = bfromcstr (""), readPtr, parm)) { 1809 | bdestroy (buff); 1810 | return NULL; 1811 | } 1812 | return buff; 1813 | } 1814 | 1815 | /* int bassigngets (bstring b, bNgetc getcPtr, void * parm, char terminator) 1816 | * 1817 | * Use an fgetc-like single character stream reading function (getcPtr) to 1818 | * obtain a sequence of characters which are concatenated to the end of the 1819 | * bstring b. The stream read is terminated by the passed in terminator 1820 | * parameter. 1821 | * 1822 | * If getcPtr returns with a negative number, or the terminator character 1823 | * (which is appended) is read, then the stream reading is halted and the 1824 | * function returns with a partial result in b. If there is an empty partial 1825 | * result, 1 is returned. If no characters are read, or there is some other 1826 | * detectable error, BSTR_ERR is returned. 1827 | */ 1828 | int bassigngets (bstring b, bNgetc getcPtr, void * parm, char terminator) { 1829 | int c, d, e; 1830 | 1831 | if (b == NULL || b->mlen <= 0 || b->slen < 0 || b->mlen < b->slen || 1832 | b->mlen <= 0 || getcPtr == NULL) return BSTR_ERR; 1833 | d = 0; 1834 | e = b->mlen - 2; 1835 | 1836 | while ((c = getcPtr (parm)) >= 0) { 1837 | if (d > e) { 1838 | b->slen = d; 1839 | if (balloc (b, d + 2) != BSTR_OK) return BSTR_ERR; 1840 | e = b->mlen - 2; 1841 | } 1842 | b->data[d] = (unsigned char) c; 1843 | d++; 1844 | if (c == terminator) break; 1845 | } 1846 | 1847 | b->data[d] = (unsigned char) '\0'; 1848 | b->slen = d; 1849 | 1850 | return d == 0 && c < 0; 1851 | } 1852 | 1853 | /* int bgetsa (bstring b, bNgetc getcPtr, void * parm, char terminator) 1854 | * 1855 | * Use an fgetc-like single character stream reading function (getcPtr) to 1856 | * obtain a sequence of characters which are concatenated to the end of the 1857 | * bstring b. The stream read is terminated by the passed in terminator 1858 | * parameter. 1859 | * 1860 | * If getcPtr returns with a negative number, or the terminator character 1861 | * (which is appended) is read, then the stream reading is halted and the 1862 | * function returns with a partial result concatentated to b. If there is 1863 | * an empty partial result, 1 is returned. If no characters are read, or 1864 | * there is some other detectable error, BSTR_ERR is returned. 1865 | */ 1866 | int bgetsa (bstring b, bNgetc getcPtr, void * parm, char terminator) { 1867 | int c, d, e; 1868 | 1869 | if (b == NULL || b->mlen <= 0 || b->slen < 0 || b->mlen < b->slen || 1870 | b->mlen <= 0 || getcPtr == NULL) return BSTR_ERR; 1871 | d = b->slen; 1872 | e = b->mlen - 2; 1873 | 1874 | while ((c = getcPtr (parm)) >= 0) { 1875 | if (d > e) { 1876 | b->slen = d; 1877 | if (balloc (b, d + 2) != BSTR_OK) return BSTR_ERR; 1878 | e = b->mlen - 2; 1879 | } 1880 | b->data[d] = (unsigned char) c; 1881 | d++; 1882 | if (c == terminator) break; 1883 | } 1884 | 1885 | b->data[d] = (unsigned char) '\0'; 1886 | b->slen = d; 1887 | 1888 | return d == 0 && c < 0; 1889 | } 1890 | 1891 | /* bstring bgets (bNgetc getcPtr, void * parm, char terminator) 1892 | * 1893 | * Use an fgetc-like single character stream reading function (getcPtr) to 1894 | * obtain a sequence of characters which are concatenated into a bstring. 1895 | * The stream read is terminated by the passed in terminator function. 1896 | * 1897 | * If getcPtr returns with a negative number, or the terminator character 1898 | * (which is appended) is read, then the stream reading is halted and the 1899 | * result obtained thus far is returned. If no characters are read, or 1900 | * there is some other detectable error, NULL is returned. 1901 | */ 1902 | bstring bgets (bNgetc getcPtr, void * parm, char terminator) { 1903 | bstring buff; 1904 | 1905 | if (0 > bgetsa (buff = bfromcstr (""), getcPtr, parm, terminator) || 0 >= buff->slen) { 1906 | bdestroy (buff); 1907 | buff = NULL; 1908 | } 1909 | return buff; 1910 | } 1911 | 1912 | struct bStream { 1913 | bstring buff; /* Buffer for over-reads */ 1914 | void * parm; /* The stream handle for core stream */ 1915 | bNread readFnPtr; /* fread compatible fnptr for core stream */ 1916 | int isEOF; /* track file's EOF state */ 1917 | int maxBuffSz; 1918 | }; 1919 | 1920 | /* struct bStream * bsopen (bNread readPtr, void * parm) 1921 | * 1922 | * Wrap a given open stream (described by a fread compatible function 1923 | * pointer and stream handle) into an open bStream suitable for the bstring 1924 | * library streaming functions. 1925 | */ 1926 | struct bStream * bsopen (bNread readPtr, void * parm) { 1927 | struct bStream * s; 1928 | 1929 | if (readPtr == NULL) return NULL; 1930 | s = (struct bStream *) bstr__alloc (sizeof (struct bStream)); 1931 | if (s == NULL) return NULL; 1932 | s->parm = parm; 1933 | s->buff = bfromcstr (""); 1934 | s->readFnPtr = readPtr; 1935 | s->maxBuffSz = BS_BUFF_SZ; 1936 | s->isEOF = 0; 1937 | return s; 1938 | } 1939 | 1940 | /* int bsbufflength (struct bStream * s, int sz) 1941 | * 1942 | * Set the length of the buffer used by the bStream. If sz is zero, the 1943 | * length is not set. This function returns with the previous length. 1944 | */ 1945 | int bsbufflength (struct bStream * s, int sz) { 1946 | int oldSz; 1947 | if (s == NULL || sz < 0) return BSTR_ERR; 1948 | oldSz = s->maxBuffSz; 1949 | if (sz > 0) s->maxBuffSz = sz; 1950 | return oldSz; 1951 | } 1952 | 1953 | int bseof (const struct bStream * s) { 1954 | if (s == NULL || s->readFnPtr == NULL) return BSTR_ERR; 1955 | return s->isEOF && (s->buff->slen == 0); 1956 | } 1957 | 1958 | /* void * bsclose (struct bStream * s) 1959 | * 1960 | * Close the bStream, and return the handle to the stream that was originally 1961 | * used to open the given stream. 1962 | */ 1963 | void * bsclose (struct bStream * s) { 1964 | void * parm; 1965 | if (s == NULL) return NULL; 1966 | s->readFnPtr = NULL; 1967 | if (s->buff) bdestroy (s->buff); 1968 | s->buff = NULL; 1969 | parm = s->parm; 1970 | s->parm = NULL; 1971 | s->isEOF = 1; 1972 | bstr__free (s); 1973 | return parm; 1974 | } 1975 | 1976 | /* int bsreadlna (bstring r, struct bStream * s, char terminator) 1977 | * 1978 | * Read a bstring terminated by the terminator character or the end of the 1979 | * stream from the bStream (s) and return it into the parameter r. This 1980 | * function may read additional characters from the core stream that are not 1981 | * returned, but will be retained for subsequent read operations. 1982 | */ 1983 | int bsreadlna (bstring r, struct bStream * s, char terminator) { 1984 | int i, l, ret, rlo; 1985 | char * b; 1986 | struct tagbstring x; 1987 | 1988 | if (s == NULL || s->buff == NULL || r == NULL || r->mlen <= 0 || 1989 | r->slen < 0 || r->mlen < r->slen) return BSTR_ERR; 1990 | l = s->buff->slen; 1991 | if (BSTR_OK != balloc (s->buff, s->maxBuffSz + 1)) return BSTR_ERR; 1992 | b = (char *) s->buff->data; 1993 | x.data = (unsigned char *) b; 1994 | 1995 | /* First check if the current buffer holds the terminator */ 1996 | b[l] = terminator; /* Set sentinel */ 1997 | for (i=0; b[i] != terminator; i++) ; 1998 | if (i < l) { 1999 | x.slen = i + 1; 2000 | ret = bconcat (r, &x); 2001 | s->buff->slen = l; 2002 | if (BSTR_OK == ret) bdelete (s->buff, 0, i + 1); 2003 | return BSTR_OK; 2004 | } 2005 | 2006 | rlo = r->slen; 2007 | 2008 | /* If not then just concatenate the entire buffer to the output */ 2009 | x.slen = l; 2010 | if (BSTR_OK != bconcat (r, &x)) return BSTR_ERR; 2011 | 2012 | /* Perform direct in-place reads into the destination to allow for 2013 | the minimum of data-copies */ 2014 | for (;;) { 2015 | if (BSTR_OK != balloc (r, r->slen + s->maxBuffSz + 1)) return BSTR_ERR; 2016 | b = (char *) (r->data + r->slen); 2017 | l = (int) s->readFnPtr (b, 1, s->maxBuffSz, s->parm); 2018 | if (l <= 0) { 2019 | r->data[r->slen] = (unsigned char) '\0'; 2020 | s->buff->slen = 0; 2021 | s->isEOF = 1; 2022 | /* If nothing was read return with an error message */ 2023 | return BSTR_ERR & -(r->slen == rlo); 2024 | } 2025 | b[l] = terminator; /* Set sentinel */ 2026 | for (i=0; b[i] != terminator; i++) ; 2027 | if (i < l) break; 2028 | r->slen += l; 2029 | } 2030 | 2031 | /* Terminator found, push over-read back to buffer */ 2032 | i++; 2033 | r->slen += i; 2034 | s->buff->slen = l - i; 2035 | bstr__memcpy (s->buff->data, b + i, l - i); 2036 | r->data[r->slen] = (unsigned char) '\0'; 2037 | return BSTR_OK; 2038 | } 2039 | 2040 | /* int bsreadlnsa (bstring r, struct bStream * s, bstring term) 2041 | * 2042 | * Read a bstring terminated by any character in the term string or the end 2043 | * of the stream from the bStream (s) and return it into the parameter r. 2044 | * This function may read additional characters from the core stream that 2045 | * are not returned, but will be retained for subsequent read operations. 2046 | */ 2047 | int bsreadlnsa (bstring r, struct bStream * s, const_bstring term) { 2048 | int i, l, ret, rlo; 2049 | unsigned char * b; 2050 | struct tagbstring x; 2051 | struct charField cf; 2052 | 2053 | if (s == NULL || s->buff == NULL || r == NULL || term == NULL || 2054 | term->data == NULL || r->mlen <= 0 || r->slen < 0 || 2055 | r->mlen < r->slen) return BSTR_ERR; 2056 | if (term->slen == 1) return bsreadlna (r, s, term->data[0]); 2057 | if (term->slen < 1 || buildCharField (&cf, term)) return BSTR_ERR; 2058 | 2059 | l = s->buff->slen; 2060 | if (BSTR_OK != balloc (s->buff, s->maxBuffSz + 1)) return BSTR_ERR; 2061 | b = (unsigned char *) s->buff->data; 2062 | x.data = b; 2063 | 2064 | /* First check if the current buffer holds the terminator */ 2065 | b[l] = term->data[0]; /* Set sentinel */ 2066 | for (i=0; !testInCharField (&cf, b[i]); i++) ; 2067 | if (i < l) { 2068 | x.slen = i + 1; 2069 | ret = bconcat (r, &x); 2070 | s->buff->slen = l; 2071 | if (BSTR_OK == ret) bdelete (s->buff, 0, i + 1); 2072 | return BSTR_OK; 2073 | } 2074 | 2075 | rlo = r->slen; 2076 | 2077 | /* If not then just concatenate the entire buffer to the output */ 2078 | x.slen = l; 2079 | if (BSTR_OK != bconcat (r, &x)) return BSTR_ERR; 2080 | 2081 | /* Perform direct in-place reads into the destination to allow for 2082 | the minimum of data-copies */ 2083 | for (;;) { 2084 | if (BSTR_OK != balloc (r, r->slen + s->maxBuffSz + 1)) return BSTR_ERR; 2085 | b = (unsigned char *) (r->data + r->slen); 2086 | l = (int) s->readFnPtr (b, 1, s->maxBuffSz, s->parm); 2087 | if (l <= 0) { 2088 | r->data[r->slen] = (unsigned char) '\0'; 2089 | s->buff->slen = 0; 2090 | s->isEOF = 1; 2091 | /* If nothing was read return with an error message */ 2092 | return BSTR_ERR & -(r->slen == rlo); 2093 | } 2094 | 2095 | b[l] = term->data[0]; /* Set sentinel */ 2096 | for (i=0; !testInCharField (&cf, b[i]); i++) ; 2097 | if (i < l) break; 2098 | r->slen += l; 2099 | } 2100 | 2101 | /* Terminator found, push over-read back to buffer */ 2102 | i++; 2103 | r->slen += i; 2104 | s->buff->slen = l - i; 2105 | bstr__memcpy (s->buff->data, b + i, l - i); 2106 | r->data[r->slen] = (unsigned char) '\0'; 2107 | return BSTR_OK; 2108 | } 2109 | 2110 | /* int bsreada (bstring r, struct bStream * s, int n) 2111 | * 2112 | * Read a bstring of length n (or, if it is fewer, as many bytes as is 2113 | * remaining) from the bStream. This function may read additional 2114 | * characters from the core stream that are not returned, but will be 2115 | * retained for subsequent read operations. This function will not read 2116 | * additional characters from the core stream beyond virtual stream pointer. 2117 | */ 2118 | int bsreada (bstring r, struct bStream * s, int n) { 2119 | int l, ret, orslen; 2120 | char * b; 2121 | struct tagbstring x; 2122 | 2123 | if (s == NULL || s->buff == NULL || r == NULL || r->mlen <= 0 2124 | || r->slen < 0 || r->mlen < r->slen || n <= 0) return BSTR_ERR; 2125 | 2126 | n += r->slen; 2127 | if (n <= 0) return BSTR_ERR; 2128 | 2129 | l = s->buff->slen; 2130 | 2131 | orslen = r->slen; 2132 | 2133 | if (0 == l) { 2134 | if (s->isEOF) return BSTR_ERR; 2135 | if (r->mlen > n) { 2136 | l = (int) s->readFnPtr (r->data + r->slen, 1, n - r->slen, s->parm); 2137 | if (0 >= l || l > n - r->slen) { 2138 | s->isEOF = 1; 2139 | return BSTR_ERR; 2140 | } 2141 | r->slen += l; 2142 | r->data[r->slen] = (unsigned char) '\0'; 2143 | return 0; 2144 | } 2145 | } 2146 | 2147 | if (BSTR_OK != balloc (s->buff, s->maxBuffSz + 1)) return BSTR_ERR; 2148 | b = (char *) s->buff->data; 2149 | x.data = (unsigned char *) b; 2150 | 2151 | do { 2152 | if (l + r->slen >= n) { 2153 | x.slen = n - r->slen; 2154 | ret = bconcat (r, &x); 2155 | s->buff->slen = l; 2156 | if (BSTR_OK == ret) bdelete (s->buff, 0, x.slen); 2157 | return BSTR_ERR & -(r->slen == orslen); 2158 | } 2159 | 2160 | x.slen = l; 2161 | if (BSTR_OK != bconcat (r, &x)) break; 2162 | 2163 | l = n - r->slen; 2164 | if (l > s->maxBuffSz) l = s->maxBuffSz; 2165 | 2166 | l = (int) s->readFnPtr (b, 1, l, s->parm); 2167 | 2168 | } while (l > 0); 2169 | if (l < 0) l = 0; 2170 | if (l == 0) s->isEOF = 1; 2171 | s->buff->slen = l; 2172 | return BSTR_ERR & -(r->slen == orslen); 2173 | } 2174 | 2175 | /* int bsreadln (bstring r, struct bStream * s, char terminator) 2176 | * 2177 | * Read a bstring terminated by the terminator character or the end of the 2178 | * stream from the bStream (s) and return it into the parameter r. This 2179 | * function may read additional characters from the core stream that are not 2180 | * returned, but will be retained for subsequent read operations. 2181 | */ 2182 | int bsreadln (bstring r, struct bStream * s, char terminator) { 2183 | if (s == NULL || s->buff == NULL || r == NULL || r->mlen <= 0) 2184 | return BSTR_ERR; 2185 | if (BSTR_OK != balloc (s->buff, s->maxBuffSz + 1)) return BSTR_ERR; 2186 | r->slen = 0; 2187 | return bsreadlna (r, s, terminator); 2188 | } 2189 | 2190 | /* int bsreadlns (bstring r, struct bStream * s, bstring term) 2191 | * 2192 | * Read a bstring terminated by any character in the term string or the end 2193 | * of the stream from the bStream (s) and return it into the parameter r. 2194 | * This function may read additional characters from the core stream that 2195 | * are not returned, but will be retained for subsequent read operations. 2196 | */ 2197 | int bsreadlns (bstring r, struct bStream * s, const_bstring term) { 2198 | if (s == NULL || s->buff == NULL || r == NULL || term == NULL 2199 | || term->data == NULL || r->mlen <= 0) return BSTR_ERR; 2200 | if (term->slen == 1) return bsreadln (r, s, term->data[0]); 2201 | if (term->slen < 1) return BSTR_ERR; 2202 | if (BSTR_OK != balloc (s->buff, s->maxBuffSz + 1)) return BSTR_ERR; 2203 | r->slen = 0; 2204 | return bsreadlnsa (r, s, term); 2205 | } 2206 | 2207 | /* int bsread (bstring r, struct bStream * s, int n) 2208 | * 2209 | * Read a bstring of length n (or, if it is fewer, as many bytes as is 2210 | * remaining) from the bStream. This function may read additional 2211 | * characters from the core stream that are not returned, but will be 2212 | * retained for subsequent read operations. This function will not read 2213 | * additional characters from the core stream beyond virtual stream pointer. 2214 | */ 2215 | int bsread (bstring r, struct bStream * s, int n) { 2216 | if (s == NULL || s->buff == NULL || r == NULL || r->mlen <= 0 2217 | || n <= 0) return BSTR_ERR; 2218 | if (BSTR_OK != balloc (s->buff, s->maxBuffSz + 1)) return BSTR_ERR; 2219 | r->slen = 0; 2220 | return bsreada (r, s, n); 2221 | } 2222 | 2223 | /* int bsunread (struct bStream * s, const_bstring b) 2224 | * 2225 | * Insert a bstring into the bStream at the current position. These 2226 | * characters will be read prior to those that actually come from the core 2227 | * stream. 2228 | */ 2229 | int bsunread (struct bStream * s, const_bstring b) { 2230 | if (s == NULL || s->buff == NULL) return BSTR_ERR; 2231 | return binsert (s->buff, 0, b, (unsigned char) '?'); 2232 | } 2233 | 2234 | /* int bspeek (bstring r, const struct bStream * s) 2235 | * 2236 | * Return the currently buffered characters from the bStream that will be 2237 | * read prior to reads from the core stream. 2238 | */ 2239 | int bspeek (bstring r, const struct bStream * s) { 2240 | if (s == NULL || s->buff == NULL) return BSTR_ERR; 2241 | return bassign (r, s->buff); 2242 | } 2243 | 2244 | /* bstring bjoin (const struct bstrList * bl, const_bstring sep); 2245 | * 2246 | * Join the entries of a bstrList into one bstring by sequentially 2247 | * concatenating them with the sep string in between. If there is an error 2248 | * NULL is returned, otherwise a bstring with the correct result is returned. 2249 | */ 2250 | bstring bjoin (const struct bstrList * bl, const_bstring sep) { 2251 | bstring b; 2252 | int i, c, v; 2253 | 2254 | if (bl == NULL || bl->qty < 0) return NULL; 2255 | if (sep != NULL && (sep->slen < 0 || sep->data == NULL)) return NULL; 2256 | 2257 | for (i = 0, c = 1; i < bl->qty; i++) { 2258 | v = bl->entry[i]->slen; 2259 | if (v < 0) return NULL; /* Invalid input */ 2260 | c += v; 2261 | if (c < 0) return NULL; /* Wrap around ?? */ 2262 | } 2263 | 2264 | if (sep != NULL) c += (bl->qty - 1) * sep->slen; 2265 | 2266 | b = (bstring) bstr__alloc (sizeof (struct tagbstring)); 2267 | if (NULL == b) return NULL; /* Out of memory */ 2268 | b->data = (unsigned char *) bstr__alloc (c); 2269 | if (b->data == NULL) { 2270 | bstr__free (b); 2271 | return NULL; 2272 | } 2273 | 2274 | b->mlen = c; 2275 | b->slen = c-1; 2276 | 2277 | for (i = 0, c = 0; i < bl->qty; i++) { 2278 | if (i > 0 && sep != NULL) { 2279 | bstr__memcpy (b->data + c, sep->data, sep->slen); 2280 | c += sep->slen; 2281 | } 2282 | v = bl->entry[i]->slen; 2283 | bstr__memcpy (b->data + c, bl->entry[i]->data, v); 2284 | c += v; 2285 | } 2286 | b->data[c] = (unsigned char) '\0'; 2287 | return b; 2288 | } 2289 | 2290 | #define BSSSC_BUFF_LEN (256) 2291 | 2292 | /* int bssplitscb (struct bStream * s, const_bstring splitStr, 2293 | * int (* cb) (void * parm, int ofs, const_bstring entry), void * parm) 2294 | * 2295 | * Iterate the set of disjoint sequential substrings read from a stream 2296 | * divided by any of the characters in splitStr. An empty splitStr causes 2297 | * the whole stream to be iterated once. 2298 | * 2299 | * Note: At the point of calling the cb function, the bStream pointer is 2300 | * pointed exactly at the position right after having read the split 2301 | * character. The cb function can act on the stream by causing the bStream 2302 | * pointer to move, and bssplitscb will continue by starting the next split 2303 | * at the position of the pointer after the return from cb. 2304 | * 2305 | * However, if the cb causes the bStream s to be destroyed then the cb must 2306 | * return with a negative value, otherwise bssplitscb will continue in an 2307 | * undefined manner. 2308 | */ 2309 | int bssplitscb (struct bStream * s, const_bstring splitStr, 2310 | int (* cb) (void * parm, int ofs, const_bstring entry), void * parm) { 2311 | struct charField chrs; 2312 | bstring buff; 2313 | int i, p, ret; 2314 | 2315 | if (cb == NULL || s == NULL || s->readFnPtr == NULL 2316 | || splitStr == NULL || splitStr->slen < 0) return BSTR_ERR; 2317 | 2318 | if (NULL == (buff = bfromcstr (""))) return BSTR_ERR; 2319 | 2320 | if (splitStr->slen == 0) { 2321 | while (bsreada (buff, s, BSSSC_BUFF_LEN) >= 0) ; 2322 | if ((ret = cb (parm, 0, buff)) > 0) 2323 | ret = 0; 2324 | } else { 2325 | buildCharField (&chrs, splitStr); 2326 | ret = p = i = 0; 2327 | for (;;) { 2328 | if (i >= buff->slen) { 2329 | bsreada (buff, s, BSSSC_BUFF_LEN); 2330 | if (i >= buff->slen) { 2331 | if (0 < (ret = cb (parm, p, buff))) ret = 0; 2332 | break; 2333 | } 2334 | } 2335 | if (testInCharField (&chrs, buff->data[i])) { 2336 | struct tagbstring t; 2337 | unsigned char c; 2338 | 2339 | blk2tbstr (t, buff->data + i + 1, buff->slen - (i + 1)); 2340 | if ((ret = bsunread (s, &t)) < 0) break; 2341 | buff->slen = i; 2342 | c = buff->data[i]; 2343 | buff->data[i] = (unsigned char) '\0'; 2344 | if ((ret = cb (parm, p, buff)) < 0) break; 2345 | buff->data[i] = c; 2346 | buff->slen = 0; 2347 | p += i + 1; 2348 | i = -1; 2349 | } 2350 | i++; 2351 | } 2352 | } 2353 | 2354 | bdestroy (buff); 2355 | return ret; 2356 | } 2357 | 2358 | /* int bssplitstrcb (struct bStream * s, const_bstring splitStr, 2359 | * int (* cb) (void * parm, int ofs, const_bstring entry), void * parm) 2360 | * 2361 | * Iterate the set of disjoint sequential substrings read from a stream 2362 | * divided by the entire substring splitStr. An empty splitStr causes 2363 | * each character of the stream to be iterated. 2364 | * 2365 | * Note: At the point of calling the cb function, the bStream pointer is 2366 | * pointed exactly at the position right after having read the split 2367 | * character. The cb function can act on the stream by causing the bStream 2368 | * pointer to move, and bssplitscb will continue by starting the next split 2369 | * at the position of the pointer after the return from cb. 2370 | * 2371 | * However, if the cb causes the bStream s to be destroyed then the cb must 2372 | * return with a negative value, otherwise bssplitscb will continue in an 2373 | * undefined manner. 2374 | */ 2375 | int bssplitstrcb (struct bStream * s, const_bstring splitStr, 2376 | int (* cb) (void * parm, int ofs, const_bstring entry), void * parm) { 2377 | bstring buff; 2378 | int i, p, ret; 2379 | 2380 | if (cb == NULL || s == NULL || s->readFnPtr == NULL 2381 | || splitStr == NULL || splitStr->slen < 0) return BSTR_ERR; 2382 | 2383 | if (splitStr->slen == 1) return bssplitscb (s, splitStr, cb, parm); 2384 | 2385 | if (NULL == (buff = bfromcstr (""))) return BSTR_ERR; 2386 | 2387 | if (splitStr->slen == 0) { 2388 | for (i=0; bsreada (buff, s, BSSSC_BUFF_LEN) >= 0; i++) { 2389 | if ((ret = cb (parm, 0, buff)) < 0) { 2390 | bdestroy (buff); 2391 | return ret; 2392 | } 2393 | buff->slen = 0; 2394 | } 2395 | return BSTR_OK; 2396 | } else { 2397 | ret = p = i = 0; 2398 | for (i=p=0;;) { 2399 | if ((ret = binstr (buff, 0, splitStr)) >= 0) { 2400 | struct tagbstring t; 2401 | blk2tbstr (t, buff->data, ret); 2402 | i = ret + splitStr->slen; 2403 | if ((ret = cb (parm, p, &t)) < 0) break; 2404 | p += i; 2405 | bdelete (buff, 0, i); 2406 | } else { 2407 | bsreada (buff, s, BSSSC_BUFF_LEN); 2408 | if (bseof (s)) { 2409 | if ((ret = cb (parm, p, buff)) > 0) ret = 0; 2410 | break; 2411 | } 2412 | } 2413 | } 2414 | } 2415 | 2416 | bdestroy (buff); 2417 | return ret; 2418 | } 2419 | 2420 | /* int bstrListCreate (void) 2421 | * 2422 | * Create a bstrList. 2423 | */ 2424 | struct bstrList * bstrListCreate (void) { 2425 | struct bstrList * sl = (struct bstrList *) bstr__alloc (sizeof (struct bstrList)); 2426 | if (sl) { 2427 | sl->entry = (bstring *) bstr__alloc (1*sizeof (bstring)); 2428 | if (!sl->entry) { 2429 | bstr__free (sl); 2430 | sl = NULL; 2431 | } else { 2432 | sl->qty = 0; 2433 | sl->mlen = 1; 2434 | } 2435 | } 2436 | return sl; 2437 | } 2438 | 2439 | /* int bstrListDestroy (struct bstrList * sl) 2440 | * 2441 | * Destroy a bstrList that has been created by bsplit, bsplits or bstrListCreate. 2442 | */ 2443 | int bstrListDestroy (struct bstrList * sl) { 2444 | int i; 2445 | if (sl == NULL || sl->qty < 0) return BSTR_ERR; 2446 | for (i=0; i < sl->qty; i++) { 2447 | if (sl->entry[i]) { 2448 | bdestroy (sl->entry[i]); 2449 | sl->entry[i] = NULL; 2450 | } 2451 | } 2452 | sl->qty = -1; 2453 | sl->mlen = -1; 2454 | bstr__free (sl->entry); 2455 | sl->entry = NULL; 2456 | bstr__free (sl); 2457 | return BSTR_OK; 2458 | } 2459 | 2460 | /* int bstrListAlloc (struct bstrList * sl, int msz) 2461 | * 2462 | * Ensure that there is memory for at least msz number of entries for the 2463 | * list. 2464 | */ 2465 | int bstrListAlloc (struct bstrList * sl, int msz) { 2466 | bstring * l; 2467 | int smsz; 2468 | size_t nsz; 2469 | if (!sl || msz <= 0 || !sl->entry || sl->qty < 0 || sl->mlen <= 0 || sl->qty > sl->mlen) return BSTR_ERR; 2470 | if (sl->mlen >= msz) return BSTR_OK; 2471 | smsz = snapUpSize (msz); 2472 | nsz = ((size_t) smsz) * sizeof (bstring); 2473 | if (nsz < (size_t) smsz) return BSTR_ERR; 2474 | l = (bstring *) bstr__realloc (sl->entry, nsz); 2475 | if (!l) { 2476 | smsz = msz; 2477 | nsz = ((size_t) smsz) * sizeof (bstring); 2478 | l = (bstring *) bstr__realloc (sl->entry, nsz); 2479 | if (!l) return BSTR_ERR; 2480 | } 2481 | sl->mlen = smsz; 2482 | sl->entry = l; 2483 | return BSTR_OK; 2484 | } 2485 | 2486 | /* int bstrListAllocMin (struct bstrList * sl, int msz) 2487 | * 2488 | * Try to allocate the minimum amount of memory for the list to include at 2489 | * least msz entries or sl->qty whichever is greater. 2490 | */ 2491 | int bstrListAllocMin (struct bstrList * sl, int msz) { 2492 | bstring * l; 2493 | size_t nsz; 2494 | if (!sl || msz <= 0 || !sl->entry || sl->qty < 0 || sl->mlen <= 0 || sl->qty > sl->mlen) return BSTR_ERR; 2495 | if (msz < sl->qty) msz = sl->qty; 2496 | if (sl->mlen == msz) return BSTR_OK; 2497 | nsz = ((size_t) msz) * sizeof (bstring); 2498 | if (nsz < (size_t) msz) return BSTR_ERR; 2499 | l = (bstring *) bstr__realloc (sl->entry, nsz); 2500 | if (!l) return BSTR_ERR; 2501 | sl->mlen = msz; 2502 | sl->entry = l; 2503 | return BSTR_OK; 2504 | } 2505 | 2506 | /* int bsplitcb (const_bstring str, unsigned char splitChar, int pos, 2507 | * int (* cb) (void * parm, int ofs, int len), void * parm) 2508 | * 2509 | * Iterate the set of disjoint sequential substrings over str divided by the 2510 | * character in splitChar. 2511 | * 2512 | * Note: Non-destructive modification of str from within the cb function 2513 | * while performing this split is not undefined. bsplitcb behaves in 2514 | * sequential lock step with calls to cb. I.e., after returning from a cb 2515 | * that return a non-negative integer, bsplitcb continues from the position 2516 | * 1 character after the last detected split character and it will halt 2517 | * immediately if the length of str falls below this point. However, if the 2518 | * cb function destroys str, then it *must* return with a negative value, 2519 | * otherwise bsplitcb will continue in an undefined manner. 2520 | */ 2521 | int bsplitcb (const_bstring str, unsigned char splitChar, int pos, 2522 | int (* cb) (void * parm, int ofs, int len), void * parm) { 2523 | int i, p, ret; 2524 | 2525 | if (cb == NULL || str == NULL || pos < 0 || pos > str->slen) 2526 | return BSTR_ERR; 2527 | 2528 | p = pos; 2529 | do { 2530 | for (i=p; i < str->slen; i++) { 2531 | if (str->data[i] == splitChar) break; 2532 | } 2533 | if ((ret = cb (parm, p, i - p)) < 0) return ret; 2534 | p = i + 1; 2535 | } while (p <= str->slen); 2536 | return BSTR_OK; 2537 | } 2538 | 2539 | /* int bsplitscb (const_bstring str, const_bstring splitStr, int pos, 2540 | * int (* cb) (void * parm, int ofs, int len), void * parm) 2541 | * 2542 | * Iterate the set of disjoint sequential substrings over str divided by any 2543 | * of the characters in splitStr. An empty splitStr causes the whole str to 2544 | * be iterated once. 2545 | * 2546 | * Note: Non-destructive modification of str from within the cb function 2547 | * while performing this split is not undefined. bsplitscb behaves in 2548 | * sequential lock step with calls to cb. I.e., after returning from a cb 2549 | * that return a non-negative integer, bsplitscb continues from the position 2550 | * 1 character after the last detected split character and it will halt 2551 | * immediately if the length of str falls below this point. However, if the 2552 | * cb function destroys str, then it *must* return with a negative value, 2553 | * otherwise bsplitscb will continue in an undefined manner. 2554 | */ 2555 | int bsplitscb (const_bstring str, const_bstring splitStr, int pos, 2556 | int (* cb) (void * parm, int ofs, int len), void * parm) { 2557 | struct charField chrs; 2558 | int i, p, ret; 2559 | 2560 | if (cb == NULL || str == NULL || pos < 0 || pos > str->slen 2561 | || splitStr == NULL || splitStr->slen < 0) return BSTR_ERR; 2562 | if (splitStr->slen == 0) { 2563 | if ((ret = cb (parm, 0, str->slen)) > 0) ret = 0; 2564 | return ret; 2565 | } 2566 | 2567 | if (splitStr->slen == 1) 2568 | return bsplitcb (str, splitStr->data[0], pos, cb, parm); 2569 | 2570 | buildCharField (&chrs, splitStr); 2571 | 2572 | p = pos; 2573 | do { 2574 | for (i=p; i < str->slen; i++) { 2575 | if (testInCharField (&chrs, str->data[i])) break; 2576 | } 2577 | if ((ret = cb (parm, p, i - p)) < 0) return ret; 2578 | p = i + 1; 2579 | } while (p <= str->slen); 2580 | return BSTR_OK; 2581 | } 2582 | 2583 | /* int bsplitstrcb (const_bstring str, const_bstring splitStr, int pos, 2584 | * int (* cb) (void * parm, int ofs, int len), void * parm) 2585 | * 2586 | * Iterate the set of disjoint sequential substrings over str divided by the 2587 | * substring splitStr. An empty splitStr causes the whole str to be 2588 | * iterated once. 2589 | * 2590 | * Note: Non-destructive modification of str from within the cb function 2591 | * while performing this split is not undefined. bsplitstrcb behaves in 2592 | * sequential lock step with calls to cb. I.e., after returning from a cb 2593 | * that return a non-negative integer, bsplitscb continues from the position 2594 | * 1 character after the last detected split character and it will halt 2595 | * immediately if the length of str falls below this point. However, if the 2596 | * cb function destroys str, then it *must* return with a negative value, 2597 | * otherwise bsplitscb will continue in an undefined manner. 2598 | */ 2599 | int bsplitstrcb (const_bstring str, const_bstring splitStr, int pos, 2600 | int (* cb) (void * parm, int ofs, int len), void * parm) { 2601 | int i, p, ret; 2602 | 2603 | if (cb == NULL || str == NULL || pos < 0 || pos > str->slen 2604 | || splitStr == NULL || splitStr->slen < 0) return BSTR_ERR; 2605 | 2606 | if (0 == splitStr->slen) { 2607 | for (i=pos; i < str->slen; i++) { 2608 | if ((ret = cb (parm, i, 1)) < 0) return ret; 2609 | } 2610 | return BSTR_OK; 2611 | } 2612 | 2613 | if (splitStr->slen == 1) 2614 | return bsplitcb (str, splitStr->data[0], pos, cb, parm); 2615 | 2616 | for (i=p=pos; i <= str->slen - splitStr->slen; i++) { 2617 | if (0 == bstr__memcmp (splitStr->data, str->data + i, splitStr->slen)) { 2618 | if ((ret = cb (parm, p, i - p)) < 0) return ret; 2619 | i += splitStr->slen; 2620 | p = i; 2621 | } 2622 | } 2623 | if ((ret = cb (parm, p, str->slen - p)) < 0) return ret; 2624 | return BSTR_OK; 2625 | } 2626 | 2627 | struct genBstrList { 2628 | bstring b; 2629 | struct bstrList * bl; 2630 | }; 2631 | 2632 | static int bscb (void * parm, int ofs, int len) { 2633 | struct genBstrList * g = (struct genBstrList *) parm; 2634 | if (g->bl->qty >= g->bl->mlen) { 2635 | int mlen = g->bl->mlen * 2; 2636 | bstring * tbl; 2637 | 2638 | while (g->bl->qty >= mlen) { 2639 | if (mlen < g->bl->mlen) return BSTR_ERR; 2640 | mlen += mlen; 2641 | } 2642 | 2643 | tbl = (bstring *) bstr__realloc (g->bl->entry, sizeof (bstring) * mlen); 2644 | if (tbl == NULL) return BSTR_ERR; 2645 | 2646 | g->bl->entry = tbl; 2647 | g->bl->mlen = mlen; 2648 | } 2649 | 2650 | g->bl->entry[g->bl->qty] = bmidstr (g->b, ofs, len); 2651 | g->bl->qty++; 2652 | return BSTR_OK; 2653 | } 2654 | 2655 | /* struct bstrList * bsplit (const_bstring str, unsigned char splitChar) 2656 | * 2657 | * Create an array of sequential substrings from str divided by the character 2658 | * splitChar. 2659 | */ 2660 | struct bstrList * bsplit (const_bstring str, unsigned char splitChar) { 2661 | struct genBstrList g; 2662 | 2663 | if (str == NULL || str->data == NULL || str->slen < 0) return NULL; 2664 | 2665 | g.bl = (struct bstrList *) bstr__alloc (sizeof (struct bstrList)); 2666 | if (g.bl == NULL) return NULL; 2667 | g.bl->mlen = 4; 2668 | g.bl->entry = (bstring *) bstr__alloc (g.bl->mlen * sizeof (bstring)); 2669 | if (NULL == g.bl->entry) { 2670 | bstr__free (g.bl); 2671 | return NULL; 2672 | } 2673 | 2674 | g.b = (bstring) str; 2675 | g.bl->qty = 0; 2676 | if (bsplitcb (str, splitChar, 0, bscb, &g) < 0) { 2677 | bstrListDestroy (g.bl); 2678 | return NULL; 2679 | } 2680 | return g.bl; 2681 | } 2682 | 2683 | /* struct bstrList * bsplitstr (const_bstring str, const_bstring splitStr) 2684 | * 2685 | * Create an array of sequential substrings from str divided by the entire 2686 | * substring splitStr. 2687 | */ 2688 | struct bstrList * bsplitstr (const_bstring str, const_bstring splitStr) { 2689 | struct genBstrList g; 2690 | 2691 | if (str == NULL || str->data == NULL || str->slen < 0) return NULL; 2692 | 2693 | g.bl = (struct bstrList *) bstr__alloc (sizeof (struct bstrList)); 2694 | if (g.bl == NULL) return NULL; 2695 | g.bl->mlen = 4; 2696 | g.bl->entry = (bstring *) bstr__alloc (g.bl->mlen * sizeof (bstring)); 2697 | if (NULL == g.bl->entry) { 2698 | bstr__free (g.bl); 2699 | return NULL; 2700 | } 2701 | 2702 | g.b = (bstring) str; 2703 | g.bl->qty = 0; 2704 | if (bsplitstrcb (str, splitStr, 0, bscb, &g) < 0) { 2705 | bstrListDestroy (g.bl); 2706 | return NULL; 2707 | } 2708 | return g.bl; 2709 | } 2710 | 2711 | /* struct bstrList * bsplits (const_bstring str, bstring splitStr) 2712 | * 2713 | * Create an array of sequential substrings from str divided by any of the 2714 | * characters in splitStr. An empty splitStr causes a single entry bstrList 2715 | * containing a copy of str to be returned. 2716 | */ 2717 | struct bstrList * bsplits (const_bstring str, const_bstring splitStr) { 2718 | struct genBstrList g; 2719 | 2720 | if ( str == NULL || str->slen < 0 || str->data == NULL || 2721 | splitStr == NULL || splitStr->slen < 0 || splitStr->data == NULL) 2722 | return NULL; 2723 | 2724 | g.bl = (struct bstrList *) bstr__alloc (sizeof (struct bstrList)); 2725 | if (g.bl == NULL) return NULL; 2726 | g.bl->mlen = 4; 2727 | g.bl->entry = (bstring *) bstr__alloc (g.bl->mlen * sizeof (bstring)); 2728 | if (NULL == g.bl->entry) { 2729 | bstr__free (g.bl); 2730 | return NULL; 2731 | } 2732 | g.b = (bstring) str; 2733 | g.bl->qty = 0; 2734 | 2735 | if (bsplitscb (str, splitStr, 0, bscb, &g) < 0) { 2736 | bstrListDestroy (g.bl); 2737 | return NULL; 2738 | } 2739 | return g.bl; 2740 | } 2741 | 2742 | #if defined (__TURBOC__) && !defined (__BORLANDC__) 2743 | # ifndef BSTRLIB_NOVSNP 2744 | # define BSTRLIB_NOVSNP 2745 | # endif 2746 | #endif 2747 | 2748 | /* Give WATCOM C/C++, MSVC some latitude for their non-support of vsnprintf */ 2749 | #if defined(__WATCOMC__) || defined(_MSC_VER) 2750 | #define exvsnprintf(r,b,n,f,a) {r = _vsnprintf (b,n,f,a);} 2751 | #else 2752 | #ifdef BSTRLIB_NOVSNP 2753 | /* This is just a hack. If you are using a system without a vsnprintf, it is 2754 | not recommended that bformat be used at all. */ 2755 | #define exvsnprintf(r,b,n,f,a) {vsprintf (b,f,a); r = -1;} 2756 | #define START_VSNBUFF (256) 2757 | #else 2758 | 2759 | #ifdef __GNUC__ 2760 | /* Something is making gcc complain about this prototype not being here, so 2761 | I've just gone ahead and put it in. */ 2762 | extern int vsnprintf (char *buf, size_t count, const char *format, va_list arg); 2763 | #endif 2764 | 2765 | #define exvsnprintf(r,b,n,f,a) {r = vsnprintf (b,n,f,a);} 2766 | #endif 2767 | #endif 2768 | 2769 | #if !defined (BSTRLIB_NOVSNP) 2770 | 2771 | #ifndef START_VSNBUFF 2772 | #define START_VSNBUFF (16) 2773 | #endif 2774 | 2775 | /* On IRIX vsnprintf returns n-1 when the operation would overflow the target 2776 | buffer, WATCOM and MSVC both return -1, while C99 requires that the 2777 | returned value be exactly what the length would be if the buffer would be 2778 | large enough. This leads to the idea that if the return value is larger 2779 | than n, then changing n to the return value will reduce the number of 2780 | iterations required. */ 2781 | 2782 | /* int bformata (bstring b, const char * fmt, ...) 2783 | * 2784 | * After the first parameter, it takes the same parameters as printf (), but 2785 | * rather than outputting results to stdio, it appends the results to 2786 | * a bstring which contains what would have been output. Note that if there 2787 | * is an early generation of a '\0' character, the bstring will be truncated 2788 | * to this end point. 2789 | */ 2790 | int bformata (bstring b, const char * fmt, ...) { 2791 | va_list arglist; 2792 | bstring buff; 2793 | int n, r; 2794 | 2795 | if (b == NULL || fmt == NULL || b->data == NULL || b->mlen <= 0 2796 | || b->slen < 0 || b->slen > b->mlen) return BSTR_ERR; 2797 | 2798 | /* Since the length is not determinable beforehand, a search is 2799 | performed using the truncating "vsnprintf" call (to avoid buffer 2800 | overflows) on increasing potential sizes for the output result. */ 2801 | 2802 | if ((n = (int) (2*strlen (fmt))) < START_VSNBUFF) n = START_VSNBUFF; 2803 | if (NULL == (buff = bfromcstralloc (n + 2, ""))) { 2804 | n = 1; 2805 | if (NULL == (buff = bfromcstralloc (n + 2, ""))) return BSTR_ERR; 2806 | } 2807 | 2808 | for (;;) { 2809 | va_start (arglist, fmt); 2810 | exvsnprintf (r, (char *) buff->data, n + 1, fmt, arglist); 2811 | va_end (arglist); 2812 | 2813 | buff->data[n] = (unsigned char) '\0'; 2814 | buff->slen = (int) (strlen) ((char *) buff->data); 2815 | 2816 | if (buff->slen < n) break; 2817 | 2818 | if (r > n) n = r; else n += n; 2819 | 2820 | if (BSTR_OK != balloc (buff, n + 2)) { 2821 | bdestroy (buff); 2822 | return BSTR_ERR; 2823 | } 2824 | } 2825 | 2826 | r = bconcat (b, buff); 2827 | bdestroy (buff); 2828 | return r; 2829 | } 2830 | 2831 | /* int bassignformat (bstring b, const char * fmt, ...) 2832 | * 2833 | * After the first parameter, it takes the same parameters as printf (), but 2834 | * rather than outputting results to stdio, it outputs the results to 2835 | * the bstring parameter b. Note that if there is an early generation of a 2836 | * '\0' character, the bstring will be truncated to this end point. 2837 | */ 2838 | int bassignformat (bstring b, const char * fmt, ...) { 2839 | va_list arglist; 2840 | bstring buff; 2841 | int n, r; 2842 | 2843 | if (b == NULL || fmt == NULL || b->data == NULL || b->mlen <= 0 2844 | || b->slen < 0 || b->slen > b->mlen) return BSTR_ERR; 2845 | 2846 | /* Since the length is not determinable beforehand, a search is 2847 | performed using the truncating "vsnprintf" call (to avoid buffer 2848 | overflows) on increasing potential sizes for the output result. */ 2849 | 2850 | if ((n = (int) (2*strlen (fmt))) < START_VSNBUFF) n = START_VSNBUFF; 2851 | if (NULL == (buff = bfromcstralloc (n + 2, ""))) { 2852 | n = 1; 2853 | if (NULL == (buff = bfromcstralloc (n + 2, ""))) return BSTR_ERR; 2854 | } 2855 | 2856 | for (;;) { 2857 | va_start (arglist, fmt); 2858 | exvsnprintf (r, (char *) buff->data, n + 1, fmt, arglist); 2859 | va_end (arglist); 2860 | 2861 | buff->data[n] = (unsigned char) '\0'; 2862 | buff->slen = (int) (strlen) ((char *) buff->data); 2863 | 2864 | if (buff->slen < n) break; 2865 | 2866 | if (r > n) n = r; else n += n; 2867 | 2868 | if (BSTR_OK != balloc (buff, n + 2)) { 2869 | bdestroy (buff); 2870 | return BSTR_ERR; 2871 | } 2872 | } 2873 | 2874 | r = bassign (b, buff); 2875 | bdestroy (buff); 2876 | return r; 2877 | } 2878 | 2879 | /* bstring bformat (const char * fmt, ...) 2880 | * 2881 | * Takes the same parameters as printf (), but rather than outputting results 2882 | * to stdio, it forms a bstring which contains what would have been output. 2883 | * Note that if there is an early generation of a '\0' character, the 2884 | * bstring will be truncated to this end point. 2885 | */ 2886 | bstring bformat (const char * fmt, ...) { 2887 | va_list arglist; 2888 | bstring buff; 2889 | int n, r; 2890 | 2891 | if (fmt == NULL) return NULL; 2892 | 2893 | /* Since the length is not determinable beforehand, a search is 2894 | performed using the truncating "vsnprintf" call (to avoid buffer 2895 | overflows) on increasing potential sizes for the output result. */ 2896 | 2897 | if ((n = (int) (2*strlen (fmt))) < START_VSNBUFF) n = START_VSNBUFF; 2898 | if (NULL == (buff = bfromcstralloc (n + 2, ""))) { 2899 | n = 1; 2900 | if (NULL == (buff = bfromcstralloc (n + 2, ""))) return NULL; 2901 | } 2902 | 2903 | for (;;) { 2904 | va_start (arglist, fmt); 2905 | exvsnprintf (r, (char *) buff->data, n + 1, fmt, arglist); 2906 | va_end (arglist); 2907 | 2908 | buff->data[n] = (unsigned char) '\0'; 2909 | buff->slen = (int) (strlen) ((char *) buff->data); 2910 | 2911 | if (buff->slen < n) break; 2912 | 2913 | if (r > n) n = r; else n += n; 2914 | 2915 | if (BSTR_OK != balloc (buff, n + 2)) { 2916 | bdestroy (buff); 2917 | return NULL; 2918 | } 2919 | } 2920 | 2921 | return buff; 2922 | } 2923 | 2924 | /* int bvcformata (bstring b, int count, const char * fmt, va_list arglist) 2925 | * 2926 | * The bvcformata function formats data under control of the format control 2927 | * string fmt and attempts to append the result to b. The fmt parameter is 2928 | * the same as that of the printf function. The variable argument list is 2929 | * replaced with arglist, which has been initialized by the va_start macro. 2930 | * The size of the appended output is upper bounded by count. If the 2931 | * required output exceeds count, the string b is not augmented with any 2932 | * contents and a value below BSTR_ERR is returned. If a value below -count 2933 | * is returned then it is recommended that the negative of this value be 2934 | * used as an update to the count in a subsequent pass. On other errors, 2935 | * such as running out of memory, parameter errors or numeric wrap around 2936 | * BSTR_ERR is returned. BSTR_OK is returned when the output is successfully 2937 | * generated and appended to b. 2938 | * 2939 | * Note: There is no sanity checking of arglist, and this function is 2940 | * destructive of the contents of b from the b->slen point onward. If there 2941 | * is an early generation of a '\0' character, the bstring will be truncated 2942 | * to this end point. 2943 | */ 2944 | int bvcformata (bstring b, int count, const char * fmt, va_list arg) { 2945 | int n, r, l; 2946 | 2947 | if (b == NULL || fmt == NULL || count <= 0 || b->data == NULL 2948 | || b->mlen <= 0 || b->slen < 0 || b->slen > b->mlen) return BSTR_ERR; 2949 | 2950 | if (count > (n = b->slen + count) + 2) return BSTR_ERR; 2951 | if (BSTR_OK != balloc (b, n + 2)) return BSTR_ERR; 2952 | 2953 | exvsnprintf (r, (char *) b->data + b->slen, count + 2, fmt, arg); 2954 | 2955 | /* Did the operation complete successfully within bounds? */ 2956 | for (l = b->slen; l <= n; l++) { 2957 | if ('\0' == b->data[l]) { 2958 | b->slen = l; 2959 | return BSTR_OK; 2960 | } 2961 | } 2962 | 2963 | /* Abort, since the buffer was not large enough. The return value 2964 | tries to help set what the retry length should be. */ 2965 | 2966 | b->data[b->slen] = '\0'; 2967 | if (r > count + 1) { /* Does r specify a particular target length? */ 2968 | n = r; 2969 | } else { 2970 | n = count + count; /* If not, just double the size of count */ 2971 | if (count > n) n = INT_MAX; 2972 | } 2973 | n = -n; 2974 | 2975 | if (n > BSTR_ERR-1) n = BSTR_ERR-1; 2976 | return n; 2977 | } 2978 | 2979 | #endif 2980 | --------------------------------------------------------------------------------