From e0eb0f4c230b98dc4ba9ec92ccd485e6a9fb4115 Mon Sep 17 00:00:00 2001 From: Allan Nathanson <42244061+Allan-N@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:02:53 -0500 Subject: [PATCH] Allow connection using "hostname", allow logging during session setup - updated ami_connect() to allow the host to be specified by DNS "name" (and not just IP address) - the ami_set_debug_level() function currently requires a session to be specified. This means that there is no way to control the debug level (and any assciated outut) before a session is established. Updated the code to allow one to pass a NULL session to set the default (and pre-session) debug level. - Allow compilation on macOS --- Makefile | 41 ++++++++++------- amicli.c | 2 +- cami.c | 123 ++++++++++++++++++++++++++++++++++++++++--------- include/cami.h | 3 +- simpleami.c | 9 +++- 5 files changed, 134 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index 444bc4a..3344e96 100644 --- a/Makefile +++ b/Makefile @@ -7,38 +7,43 @@ # CC = gcc -CFLAGS = -Wall -Werror -Wno-unused-parameter -Wextra -Wstrict-prototypes -Wmissing-prototypes -Wdeclaration-after-statement -Wmissing-declarations -Wmissing-format-attribute -Wformat=2 -Wshadow -std=gnu99 -pthread -O3 -g -Wstack-protector -fno-omit-frame-pointer -D_FORTIFY_SOURCE=2 +CFLAGS = -Wall -Werror -Wno-unused-parameter -Wextra -Wstrict-prototypes -Wmissing-prototypes -Wdeclaration-after-statement -Wmissing-declarations -Wmissing-format-attribute -Wformat=2 -Wshadow -std=gnu99 -pthread -O0 -g -Wstack-protector -fno-omit-frame-pointer -D_FORTIFY_SOURCE=2 -I. EXE = cami SAMPEXES = simpleami amicli -LIBNAME = libcami -LIBS = -lm +LIBNAME = lib$(EXE).so +LIBS = -lm -ldl RM = rm -f -INSTALL = install +INSTALL = install -all : library +all : library examples %.o: %.c $(CC) $(CFLAGS) -fPIC -c $^ -library: $(EXE).o +$(LIBNAME): $(EXE).o @echo "== Linking $@" - $(CC) -shared -fPIC -o $(LIBNAME).so $^ $(LIBS) + $(CC) -shared -fPIC -o $(LIBNAME) $^ $(LIBS) + +library: $(LIBNAME) + @if [ ! -d /usr/include/$(EXE) ]; then \ + ln -f -s include $(EXE); \ + fi install: - $(INSTALL) -m 755 $(LIBNAME).so "/usr/lib" + $(INSTALL) -m 755 $(LIBNAME) "/usr/lib" mkdir -p /usr/include/$(EXE) - $(INSTALL) -m 755 include/*.h "/usr/include/$(EXE)/" + $(INSTALL) -m 644 include/*.h "/usr/include/$(EXE)/" -simpleami: library install simpleami.o - $(CC) $(CFLAGS) -o simpleami simpleami.o -l$(EXE) $(LIBS) -ldl +simpleami: simpleami.o $(LIBNAME) + $(CC) $(CFLAGS) -o $@ $@.o -L. -Wl,-rpath,. -l$(EXE) $(LIBS) -amicli: library install amicli.o - $(CC) $(CFLAGS) -o amicli amicli.o -l$(EXE) $(LIBS) -ldl +amicli: amicli.o $(LIBNAME) + $(CC) $(CFLAGS) -o $@ $@.o -L. -Wl,-rpath,. -l$(EXE) $(LIBS) -examples : $(SAMPEXES) +examples: $(SAMPEXES) -clean : - $(RM) *.i *.o $(EXE) $(SAMPEXES) $(LIBNAME).so +clean: + $(RM) *.i *.o $(EXE) $(SAMPEXES) $(LIBNAME) uninstall: $(RM) /usr/lib/$(EXE).so @@ -48,5 +53,7 @@ uninstall: .PHONY: all .PHONY: library .PHONY: install -.PHONY: example +.PHONY: examples .PHONY: clean + +# vim: set noexpandtab shiftwidth=4 tabstop=4: diff --git a/amicli.c b/amicli.c index d7d7100..25d282c 100644 --- a/amicli.c +++ b/amicli.c @@ -148,7 +148,7 @@ int main(int argc,char *argv[]) int debug = 0; struct ami_session *ami; - while ((c = getopt(argc, argv, getopt_settings)) != -1) { + while ((c = getopt(argc, argv, getopt_settings)) != (char) -1) { switch (c) { case '?': case 'd': diff --git a/cami.c b/cami.c index 9681c7a..4177373 100644 --- a/cami.c +++ b/cami.c @@ -55,7 +55,7 @@ if (ami->debug_level >= level) { \ struct timeval tv; \ gettimeofday(&tv, NULL); \ - if (ami->debugfd != -1) dprintf(ami->debugfd, "%llu:%03lu : %d : " fmt, (((long long)tv.tv_sec)), (tv.tv_usec/1000), __LINE__, ## __VA_ARGS__); \ + if (ami->debugfd != -1) dprintf(ami->debugfd, "%llu:%03lu : %d : " fmt, (((unsigned long long)tv.tv_sec)), (unsigned long)(tv.tv_usec/1000), __LINE__, ## __VA_ARGS__); \ } \ } @@ -101,6 +101,10 @@ struct ami_session { unsigned int return_null_on_error:1; }; +/* Used for debugging prior to session creation */ +static int ami_initial_debugfd = -1; +static int ami_initial_debug_level = 0; + static struct ami_session *ami_session_new(void) { struct ami_session *ami = calloc(1, sizeof(*ami)); @@ -108,7 +112,8 @@ static struct ami_session *ami_session_new(void) return NULL; } ami->ami_socket = -1; - ami->debugfd = -1; + ami->debugfd = ami_initial_debugfd; + ami->debug_level = ami_initial_debug_level; ami->ami_pipe[0] = ami->ami_pipe[1] = -1; ami->ami_read_pipe[0] = ami->ami_read_pipe[1] = -1; ami->ami_event_pipe[0] = ami->ami_event_pipe[1] = -1; @@ -358,6 +363,7 @@ struct ami_session *ami_connect(const char *hostname, int port, void (*callback) int fd; struct sockaddr_in saddr; struct ami_session *ami; + int ret; ami = ami_session_new(); if (!ami) { @@ -416,13 +422,57 @@ struct ami_session *ami_connect(const char *hostname, int port, void (*callback) goto cleanup; } ami->ami_socket = fd; - inet_pton(AF_INET, hostname, &(saddr.sin_addr)); - saddr.sin_family = AF_INET; - saddr.sin_port = htons(port); /* use network order */ - if (connect(fd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { - ami_error(ami, "connect failed: %s\n", strerror(errno)); - ami_cleanup(ami); - goto cleanup; + if (inet_pton(AF_INET, hostname, &(saddr.sin_addr)) == 1) { + saddr.sin_family = AF_INET; + saddr.sin_port = htons(port); /* use network order */ + if (connect(fd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { + ami_error(ami, "connect failed: %s\n", strerror(errno)); + ami_cleanup(ami); + goto cleanup; + } + } else { + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_V4MAPPED | AI_ADDRCONFIG + }; + struct addrinfo *res; + + if (getaddrinfo(hostname, NULL, &hints, &res) == 0) { + if (res->ai_addr == NULL) { + freeaddrinfo(res); + ami_error(ami, "host %s not valid\n", hostname); + ami_cleanup(ami); + goto cleanup; + } + + switch (res->ai_addr->sa_family) { + case AF_INET: + ((struct sockaddr_in *)res->ai_addr)->sin_port = htons(port); + break; + case AF_INET6: + ((struct sockaddr_in6 *)res->ai_addr)->sin6_port = htons(port); + break; + default: + freeaddrinfo(res); + ami_error(ami, "address for host %s not valid\n", hostname); + ami_cleanup(ami); + goto cleanup; + } + + if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { + freeaddrinfo(res); + ami_error(ami, "connect failed: %s\n", strerror(errno)); + ami_cleanup(ami); + goto cleanup; + } + + freeaddrinfo(res); + } else { + ami_error(ami, "host %s not valid\n", hostname); + ami_cleanup(ami); + goto cleanup; + } } ami->ami_callback = callback; ami->disconnected_callback = dis_callback; @@ -438,18 +488,37 @@ struct ami_session *ami_connect(const char *hostname, int port, void (*callback) pthread_mutexattr_destroy(&attr); } - if (pthread_create(&ami->ami_thread, NULL, ami_loop, ami)) { - ami_error(ami, "Unable to create AMI thread: %s\n", strerror(errno)); - ami_cleanup(ami); - goto cleanup; + { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 2 * 1024 * 1024); + ret = pthread_create(&ami->ami_thread, &attr, ami_loop, ami); + pthread_attr_destroy(&attr); + if (ret) { + ami_error(ami, "Unable to create AMI thread: %s\n", strerror(errno)); + ami_cleanup(ami); + goto cleanup; + } } - if (pthread_create(&ami->dispatch_thread, NULL, ami_event_dispatch, ami)) { - ami_error(ami, "Unable to create dispatch thread: %s\n", strerror(errno)); - ami_cleanup(ami); - goto cleanup; + + { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 2 * 1024 * 1024); + ret = pthread_create(&ami->dispatch_thread, &attr, ami_event_dispatch, ami); + pthread_attr_destroy(&attr); + if (ret) { + ami_error(ami, "Unable to create dispatch thread: %s\n", strerror(errno)); + ami_cleanup(ami); + goto cleanup; + } } pthread_mutex_unlock(&ami->ami_read_lock); + + /* establish the initial per-session debug fd and level */ + ami->debugfd = -1; + ami->debug_level = 0; return ami; cleanup: @@ -506,20 +575,28 @@ void ami_destroy(struct ami_session *ami) void ami_set_debug(struct ami_session *ami, int fd) { - ami->debugfd = fd; + if (ami) { + ami->debugfd = fd; + } else { + ami_initial_debugfd = fd; + } } int ami_set_debug_level(struct ami_session *ami, int level) { - int old_level = ami->debug_level; + int old_level = ami ? ami->debug_level : ami_initial_debug_level; if (level < 0 || level > 10) { return -1; } - ami->debug_level = level; + if (ami) { + ami->debug_level = level; + } else { + ami_initial_debug_level = level; + } return old_level; } -static int __attribute__ ((format (gnu_printf, 4, 5))) __ami_send(struct ami_session *ami, va_list ap, const char *fmt, const char *prefmt, ...) +static int __attribute__ ((format (printf, 3, 0))) __attribute__ ((format (printf, 4, 5))) __ami_send(struct ami_session *ami, va_list ap, const char *fmt, const char *prefmt, ...) { int res = 0; int bytes = 0; @@ -1040,7 +1117,7 @@ static int ami_wait_for_response(struct ami_session *ami, int msgid) } } -static int ami_send(struct ami_session *ami, const char *action, const char *fmt, ...) +static int __attribute__ ((format (printf, 3, 4))) ami_send(struct ami_session *ami, const char *action, const char *fmt, ...) { int res; @@ -1053,7 +1130,7 @@ static int ami_send(struct ami_session *ami, const char *action, const char *fmt return res; } -struct ami_response *ami_action(struct ami_session *ami, const char *action, const char *fmt, ...) +struct ami_response * __attribute__ ((format (printf, 3, 4))) ami_action(struct ami_session *ami, const char *action, const char *fmt, ...) { struct ami_response *resp = NULL; /* Remember: no trailing \r\n in fmt !*/ diff --git a/include/cami.h b/include/cami.h index 80f77a4..9c5d982 100644 --- a/include/cami.h +++ b/include/cami.h @@ -50,7 +50,7 @@ struct ami_response { /*! * \brief Enable debug logging - * \param ami + * \param ami The AMI session. If NULL, sets the file descriptor for debug logging prior to session creation (e.g. in ami_connect) * \param fd File descriptor to which optional debug log messages should be delivered. Default is off (-1) * \note This is not recommended for use in production, but may be helpful in a dev environment. */ @@ -59,6 +59,7 @@ void ami_set_debug(struct ami_session *ami, int fd); /*! * \brief Set debug logging level * \param ami + * \param ami The AMI session. If NULL, sets the debug level prior to session creation (e.g. in ami_connect) * \param level Level between 0 and 10. 0 will disable logging, 10 is the most granular. Default is 0. * \note A log level of 1 is recommended for production use: this will log all errors and warnings. Use a greater log level for debugging. * \retval -1 on failure, non-negative old log level otherwise diff --git a/simpleami.c b/simpleami.c index 053720c..ea44072 100644 --- a/simpleami.c +++ b/simpleami.c @@ -36,6 +36,8 @@ static void simple_callback(struct ami_session *ami, struct ami_event *event) { const char *eventname = ami_keyvalue(event, "Event"); + (void) ami; + printf("(Callback) Event Received: %s\n", eventname); #if 0 /* Or, you could print out the entire event contents for debugging, or to see what's there: */ @@ -57,8 +59,8 @@ static int simple_ami(const char *hostname, const char *username, const char *pa struct ami_session *ami; struct ami_response *resp = NULL; #if 0 - ami_set_debug(STDERR_FILENO); /* Not recommended for daemon programs */ - ami_set_debug_level(1); + ami_set_debug(NULL, STDERR_FILENO); /* Not recommended for daemon programs */ + ami_set_debug_level(NULL, 1); #endif ami = ami_connect(hostname, 0, simple_callback, simple_disconnect_callback); if (!ami) { @@ -95,6 +97,9 @@ static int simple_ami(const char *hostname, const char *username, const char *pa int main(int argc,char *argv[]) { + (void) argc; + (void) argv; + if (simple_ami("127.0.0.1", "test", "test")) { exit(EXIT_FAILURE); }