From 1ce4acb85a3bcb953c0f9cc91d2edc855969948b Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Mon, 22 Aug 2022 13:40:27 +0200 Subject: add some code A basic client-server app, the client sends commands as an array of strings. Hopefully I didn't mess up, and hopefully it'll be useful. --- src/.clang-format | 12 +++ src/CMakeLists.txt | 16 +++ src/client.c | 31 ++++++ src/client.h | 18 ++++ src/client_main.c | 86 +++++++++++++++++ src/cmd.c | 160 ++++++++++++++++++++++++++++++ src/cmd.h | 19 ++++ src/const.h | 7 ++ src/log.h | 20 ++++ src/net.c | 278 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/net.h | 20 ++++ src/server.c | 48 +++++++++ src/server.h | 17 ++++ src/server_main.c | 82 ++++++++++++++++ 14 files changed, 814 insertions(+) create mode 100644 src/.clang-format create mode 100644 src/CMakeLists.txt create mode 100644 src/client.c create mode 100644 src/client.h create mode 100644 src/client_main.c create mode 100644 src/cmd.c create mode 100644 src/cmd.h create mode 100644 src/const.h create mode 100644 src/log.h create mode 100644 src/net.c create mode 100644 src/net.h create mode 100644 src/server.c create mode 100644 src/server.h create mode 100644 src/server_main.c (limited to 'src') diff --git a/src/.clang-format b/src/.clang-format new file mode 100644 index 0000000..d46a7a3 --- /dev/null +++ b/src/.clang-format @@ -0,0 +1,12 @@ +--- +# The "Linux kernel" style, borrowed and modified from +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html#examples +BasedOnStyle: LLVM +IndentWidth: 8 +UseTab: AlignWithSpaces +BreakBeforeBraces: Linux +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false + +ColumnLimit: 100 +AllowShortFunctionsOnASingleLine: Empty diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..61fdfa6 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.0) + +project(tinyCI VERSION 0.0.1 LANGUAGES C) + +if(MSVC) + add_compile_options(/W4 /WX) +else() + add_compile_options(-Wall -Wextra -Werror) +endif() + +add_compile_definitions(_GNU_SOURCE) + +add_compile_definitions(VERSION="${PROJECT_VERSION}") + +add_executable(server server_main.c server.c cmd.c net.c) +add_executable(client client_main.c client.c cmd.c net.c) diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..644cb13 --- /dev/null +++ b/src/client.c @@ -0,0 +1,31 @@ +#include "client.h" +#include "cmd.h" +#include "net.h" + +#include + +int client_create(struct client *client, const struct settings *settings) +{ + client->fd = connect_to_host(settings->host, settings->port); + if (client->fd < 0) + return client->fd; + + return 0; +} + +void client_destroy(const struct client *client) +{ + close(client->fd); +} + +int client_main(const struct client *client, int argc, char *argv[]) +{ + int result, ret = 0; + struct cmd cmd = {argc, argv}; + + ret = cmd_send_and_wait_for_result(client->fd, &cmd, &result); + if (ret < 0) + return ret; + + return result; +} diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..05dec85 --- /dev/null +++ b/src/client.h @@ -0,0 +1,18 @@ +#ifndef __CLIENT_H__ +#define __CLIENT_H__ + +struct settings { + const char *host; + const char *port; +}; + +struct client { + int fd; +}; + +int client_create(struct client *, const struct settings *); +void client_destroy(const struct client *); + +int client_main(const struct client *, int argc, char *argv[]); + +#endif diff --git a/src/client_main.c b/src/client_main.c new file mode 100644 index 0000000..0bf4979 --- /dev/null +++ b/src/client_main.c @@ -0,0 +1,86 @@ +#include "client.h" +#include "const.h" + +#include +#include +#include + +static struct settings default_settings() +{ + struct settings settings = {DEFAULT_HOST, DEFAULT_PORT}; + return settings; +} + +static void print_usage(const char *argv0) +{ + printf("usage: %s [-h|--help] [-V|--version] [-H|--host HOST] [-p|--port PORT]\n", argv0); +} + +static void print_version() +{ + printf("%s\n", VERSION); +} + +static int parse_settings(struct settings *settings, int argc, char *argv[]) +{ + int opt, longind; + + *settings = default_settings(); + + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {"host", required_argument, 0, 'H'}, + {"port", required_argument, 0, 'p'}, + {0, 0, 0, 0}, + }; + + while ((opt = getopt_long(argc, argv, "hVH:p:", long_options, &longind)) != -1) { + switch (opt) { + case 'h': + print_usage(argv[0]); + exit(0); + break; + case 'V': + print_version(); + exit(0); + break; + case 'H': + settings->host = optarg; + break; + case 'p': + settings->port = optarg; + break; + default: + print_usage(argv[0]); + exit(1); + break; + } + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct settings settings; + struct client client; + int ret = 0; + + ret = parse_settings(&settings, argc, argv); + if (ret < 0) + return ret; + + ret = client_create(&client, &settings); + if (ret < 0) + return ret; + + ret = client_main(&client, argc - optind, argv + optind); + if (ret < 0) + goto destroy_client; + +destroy_client: + client_destroy(&client); + + return ret; +} diff --git a/src/cmd.c b/src/cmd.c new file mode 100644 index 0000000..c3ddf6c --- /dev/null +++ b/src/cmd.c @@ -0,0 +1,160 @@ +#include "cmd.h" +#include "log.h" +#include "net.h" + +#include +#include + +static size_t calc_buf_len(int argc, char **argv) +{ + size_t len = 0; + for (int i = 0; i < argc; ++i) + len += strlen(argv[i]) + 1; + return len; +} + +static int calc_arr_len(const void *buf, size_t len) +{ + int argc = 0; + for (const char *it = buf; it < (const char *)buf + len; it += strlen(it) + 1) + ++argc; + return argc; +} + +static void arr_pack(char *dest, int argc, char **argv) +{ + for (int i = 0; i < argc; ++i) { + strcpy(dest, argv[i]); + dest += strlen(argv[i]) + 1; + } +} + +static int arr_unpack(char **argv, int argc, const char *src) +{ + for (int i = 0; i < argc; ++i) + argv[i] = NULL; + + for (int i = 0; i < argc; ++i) { + size_t len = strlen(src); + + argv[i] = malloc(len); + if (!argv[i]) { + print_errno("malloc"); + goto free; + } + + strcpy(argv[i], src); + src += len + 1; + } + + return 0; + +free: + for (int i = 0; i < argc; ++i) + if (argv[i]) + free(argv[i]); + else + break; + + return -1; +} + +int cmd_send(int fd, const struct cmd *cmd) +{ + int ret = 0; + + size_t len = calc_buf_len(cmd->argc, cmd->argv); + char *buf = malloc(len); + if (!buf) { + print_errno("malloc"); + return -1; + } + arr_pack(buf, cmd->argc, cmd->argv); + + ret = send_buf(fd, buf, len); + if (ret < 0) + goto free_buf; + +free_buf: + free(buf); + + return ret; +} + +int cmd_send_and_wait_for_result(int fd, const struct cmd *cmd, int *result) +{ + int ret = 0; + + ret = cmd_send(fd, cmd); + if (ret < 0) + return ret; + + ret = recv_static(fd, result, sizeof(*result)); + if (ret < 0) + return ret; + + return ret; +} + +int cmd_recv(int fd, struct cmd *cmd) +{ + void *buf; + size_t len; + int ret = 0; + + ret = recv_buf(fd, &buf, &len); + if (ret < 0) + return ret; + + cmd->argc = calc_arr_len(buf, len); + cmd->argv = malloc(cmd->argc * sizeof(char *)); + if (!cmd->argv) { + print_errno("malloc"); + ret = -1; + goto free_buf; + } + memset(cmd->argv, 0, cmd->argc * sizeof(char *)); + + ret = arr_unpack(cmd->argv, cmd->argc, buf); + if (ret < 0) + goto free_argv; + + goto free_buf; + +free_argv: + free(cmd->argv); + +free_buf: + free(buf); + + return ret; +} + +int cmd_recv_and_send_result(int fd, cmd_handler handler, void *arg) +{ + struct cmd cmd; + int result; + int ret = 0; + + ret = cmd_recv(fd, &cmd); + if (ret < 0) + return ret; + + result = handler(&cmd, arg); + + ret = send_buf(fd, &result, sizeof(result)); + if (ret < 0) + goto free_cmd; + +free_cmd: + cmd_free(&cmd); + + return ret; +} + +void cmd_free(const struct cmd *cmd) +{ + for (int i = 0; i < cmd->argc; ++i) + free(cmd->argv[i]); + free(cmd->argv); +} diff --git a/src/cmd.h b/src/cmd.h new file mode 100644 index 0000000..f9b3c6b --- /dev/null +++ b/src/cmd.h @@ -0,0 +1,19 @@ +#ifndef __CMD_H__ +#define __CMD_H__ + +struct cmd { + int argc; + char **argv; +}; + +int cmd_send(int fd, const struct cmd *); +int cmd_send_and_wait_for_result(int fd, const struct cmd *, int *result); + +typedef int (*cmd_handler)(const struct cmd *, void *arg); + +int cmd_recv(int fd, struct cmd *); +int cmd_recv_and_send_result(int fd, cmd_handler, void *arg); + +void cmd_free(const struct cmd *); + +#endif diff --git a/src/const.h b/src/const.h new file mode 100644 index 0000000..53b0364 --- /dev/null +++ b/src/const.h @@ -0,0 +1,7 @@ +#ifndef __CONST_H__ +#define __CONST_H__ + +#define DEFAULT_HOST "127.0.0.1" +#define DEFAULT_PORT "5556" + +#endif diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..f050048 --- /dev/null +++ b/src/log.h @@ -0,0 +1,20 @@ +#ifndef __LOG_H__ +#define __LOG_H__ + +#include +#include +#include + +#define print_errno(s) \ + { \ + fprintf(stderr, "%s(%d): ", basename(__FILE__), __LINE__); \ + perror(s); \ + } + +#define print_error(...) \ + { \ + fprintf(stderr, "%s(%d): ", basename(__FILE__), __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + } + +#endif diff --git a/src/net.c b/src/net.c new file mode 100644 index 0000000..856b120 --- /dev/null +++ b/src/net.c @@ -0,0 +1,278 @@ +#include "net.h" +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include + +#define gai_print_errno(ec) print_error("getaddrinfo: %s\n", gai_strerror(ec)) + +static int ignore_signal(int signum) +{ + int ret = 0; + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_IGN; + + ret = sigaction(signum, &act, NULL); + if (ret < 0) { + print_errno("sigaction"); + return ret; + } + + return ret; +} + +static int ignore_sigchld() +{ + return ignore_signal(SIGCHLD); +} + +int bind_to_port(const char *port) +{ + struct addrinfo *result, *it = NULL; + struct addrinfo hints; + int socket_fd, ret = 0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + ret = getaddrinfo(NULL, port, &hints, &result); + if (ret) { + gai_print_errno(ret); + return -1; + } + + for (it = result; it; it = it->ai_next) { + socket_fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (socket_fd < 0) { + print_errno("socket"); + continue; + } + + static const int yes = 1; + + if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { + print_errno("setsockopt"); + goto close_socket; + } + + if (bind(socket_fd, it->ai_addr, it->ai_addrlen) < 0) { + print_errno("bind"); + goto close_socket; + } + + break; + + close_socket: + close(socket_fd); + } + + freeaddrinfo(result); + + if (!it) { + print_error("Couldn't bind to port %s\n", port); + return -1; + } + + ret = listen(socket_fd, 4096); + if (ret < 0) { + print_errno("listen"); + goto fail; + } + + /* Don't make zombies. The alternative is wait()ing for the child in + * the signal handler. */ + ret = ignore_sigchld(); + if (ret < 0) + goto fail; + + return socket_fd; + +fail: + close(socket_fd); + + return ret; +} + +int accept_connection(int fd, connection_handler handler, void *arg) +{ + int conn_fd, ret = 0; + + ret = accept(fd, NULL, NULL); + if (ret < 0) { + print_errno("accept"); + return ret; + } + conn_fd = ret; + + pid_t child_pid = fork(); + if (child_pid < 0) { + print_errno("fork"); + ret = -1; + goto close_conn; + } + + if (!child_pid) { + close(fd); + ret = handler(conn_fd, arg); + close(conn_fd); + exit(ret); + } + +close_conn: + close(conn_fd); + + return ret; +} + +int connect_to_host(const char *host, const char *port) +{ + struct addrinfo *result, *it = NULL; + struct addrinfo hints; + int socket_fd, ret = 0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + ret = getaddrinfo(host, port, &hints, &result); + if (ret) { + gai_print_errno(ret); + return -1; + } + + for (it = result; it; it = it->ai_next) { + socket_fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (socket_fd < 0) { + print_errno("socket"); + continue; + } + + if (connect(socket_fd, it->ai_addr, it->ai_addrlen) < 0) { + print_errno("connect"); + goto close_socket; + } + + break; + + close_socket: + close(socket_fd); + } + + freeaddrinfo(result); + + if (!it) { + print_error("Couldn't connect to host %s, port %s\n", host, port); + return -1; + } + + return socket_fd; +} + +int send_all(int fd, const void *buf, size_t len) +{ + size_t sent_total = 0; + + while (sent_total < len) { + ssize_t sent_now = write(fd, (const char *)buf + sent_total, len - sent_total); + + if (sent_now < 0) { + print_errno("write"); + return -1; + } + + sent_total += sent_now; + } + + return 0; +} + +int recv_all(int fd, void *buf, size_t len) +{ + size_t read_total = 0; + + while (read_total < len) { + ssize_t read_now = read(fd, buf, len); + + if (read_now < 0) { + print_errno("read"); + return -1; + } + + read_total += read_now; + } + + return read_total; +} + +int send_buf(int fd, const void *buf, size_t len) +{ + int ret = 0; + + ret = send_all(fd, &len, sizeof(len)); + if (ret < 0) + return ret; + + ret = send_all(fd, buf, len); + if (ret < 0) + return ret; + + return ret; +} + +int recv_buf(int fd, void **buf, size_t *len) +{ + int ret = 0; + + ret = recv_all(fd, len, sizeof(*len)); + if (ret < 0) + return ret; + + *buf = malloc(*len); + if (!*buf) { + print_errno("malloc"); + return -1; + } + + ret = recv_all(fd, *buf, *len); + if (ret < 0) + goto free_buf; + + return ret; + +free_buf: + free(*buf); + + return ret; +} + +int recv_static(int fd, void *buf, size_t len) +{ + void *actual_buf; + size_t actual_len; + int ret = 0; + + ret = recv_buf(fd, &actual_buf, &actual_len); + if (ret < 0) + return ret; + + if (actual_len != len) { + print_error("Expected message length: %lu, actual: %lu\n", len, actual_len); + ret = -1; + goto free_buf; + } + + memcpy(buf, actual_buf, len); + +free_buf: + free(actual_buf); + + return ret; +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 0000000..31150e8 --- /dev/null +++ b/src/net.h @@ -0,0 +1,20 @@ +#ifndef __NET_H__ +#define __NET_H__ + +#include + +int bind_to_port(const char *port); + +typedef int (*connection_handler)(int fd, void *arg); +int accept_connection(int fd, connection_handler, void *arg); + +int connect_to_host(const char *host, const char *port); + +int send_all(int fd, const void *, size_t); +int send_buf(int fd, const void *, size_t); + +int recv_all(int fd, void *, size_t); +int recv_buf(int fd, void **, size_t *); +int recv_static(int fd, void *, size_t); + +#endif diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..91f3924 --- /dev/null +++ b/src/server.c @@ -0,0 +1,48 @@ +#include "server.h" +#include "cmd.h" +#include "net.h" + +#include +#include + +int server_create(struct server *server, const struct settings *settings) +{ + server->fd = bind_to_port(settings->port); + if (server->fd < 0) + return server->fd; + + return 0; +} + +void server_destroy(const struct server *server) +{ + close(server->fd); +} + +static int cmd_handle(const struct cmd *cmd, void *) +{ + for (int i = 0; i < cmd->argc; ++i) + printf("cmd[%d]: %s\n", i, cmd->argv[i]); + return 0; +} + +static int server_handle(int fd, void *) +{ + return cmd_recv_and_send_result(fd, cmd_handle, NULL); +} + +static int server_accept(const struct server *server) +{ + return accept_connection(server->fd, server_handle, NULL); +} + +int server_main(const struct server *server) +{ + int ret = 0; + + while (1) { + ret = server_accept(server); + if (ret < 0) + return ret; + } +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..8a46314 --- /dev/null +++ b/src/server.h @@ -0,0 +1,17 @@ +#ifndef __SERVER_H__ +#define __SERVER_H__ + +struct settings { + const char *port; +}; + +struct server { + int fd; +}; + +int server_create(struct server *, const struct settings *); +void server_destroy(const struct server *); + +int server_main(const struct server *); + +#endif diff --git a/src/server_main.c b/src/server_main.c new file mode 100644 index 0000000..b77a681 --- /dev/null +++ b/src/server_main.c @@ -0,0 +1,82 @@ +#include "const.h" +#include "server.h" + +#include +#include +#include + +static struct settings default_settings() +{ + struct settings settings = {DEFAULT_PORT}; + return settings; +} + +static void print_usage(const char *argv0) +{ + printf("usage: %s [-h|--help] [-V|--version] [-p|--port PORT]\n", argv0); +} + +static void print_version() +{ + printf("%s\n", VERSION); +} + +static int parse_settings(struct settings *settings, int argc, char *argv[]) +{ + int opt, longind; + + *settings = default_settings(); + + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {"port", required_argument, 0, 'p'}, + {0, 0, 0, 0}, + }; + + while ((opt = getopt_long(argc, argv, "hVp:", long_options, &longind)) != -1) { + switch (opt) { + case 'h': + print_usage(argv[0]); + exit(0); + break; + case 'V': + print_version(); + exit(0); + break; + case 'p': + settings->port = optarg; + break; + default: + print_usage(argv[0]); + exit(1); + break; + } + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct settings settings; + struct server server; + int ret = 0; + + ret = parse_settings(&settings, argc, argv); + if (ret < 0) + return ret; + + ret = server_create(&server, &settings); + if (ret < 0) + return ret; + + ret = server_main(&server); + if (ret < 0) + goto destroy_server; + +destroy_server: + server_destroy(&server); + + return ret; +} -- cgit v1.2.3