aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorEgor Tensin <Egor.Tensin@gmail.com>2022-08-22 13:40:27 +0200
committerEgor Tensin <Egor.Tensin@gmail.com>2022-08-23 21:44:04 +0200
commit1ce4acb85a3bcb953c0f9cc91d2edc855969948b (patch)
treec06abb52fef74d375c87a7bdf39e7777bbf1b3b1
parentadd doc/rationale.md (diff)
downloadcimple-1ce4acb85a3bcb953c0f9cc91d2edc855969948b.tar.gz
cimple-1ce4acb85a3bcb953c0f9cc91d2edc855969948b.zip
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.
-rw-r--r--src/.clang-format12
-rw-r--r--src/CMakeLists.txt16
-rw-r--r--src/client.c31
-rw-r--r--src/client.h18
-rw-r--r--src/client_main.c86
-rw-r--r--src/cmd.c160
-rw-r--r--src/cmd.h19
-rw-r--r--src/const.h7
-rw-r--r--src/log.h20
-rw-r--r--src/net.c278
-rw-r--r--src/net.h20
-rw-r--r--src/server.c48
-rw-r--r--src/server.h17
-rw-r--r--src/server_main.c82
14 files changed, 814 insertions, 0 deletions
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 <unistd.h>
+
+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 <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+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 <stdlib.h>
+#include <string.h>
+
+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 <netdb.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <netdb.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#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 <stdlib.h>
+
+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 <stdio.h>
+#include <unistd.h>
+
+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 <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+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;
+}