aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/tcp_server.c
blob: 59790b8c21ada83f3999973064960d81106c9172 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
 * Copyright (c) 2022 Egor Tensin <Egor.Tensin@gmail.com>
 * This file is part of the "cimple" project.
 * For details, see https://github.com/egor-tensin/cimple.
 * Distributed under the MIT License.
 */

#include "tcp_server.h"
#include "compiler.h"
#include "event_loop.h"
#include "log.h"
#include "net.h"
#include "signal.h"

#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>

struct tcp_server {
	int fd;
	tcp_server_conn_handler conn_handler;
	void *arg;
};

int tcp_server_create(struct tcp_server **_server, const char *port,
                      tcp_server_conn_handler conn_handler, void *arg)
{
	int ret = 0;

	struct tcp_server *server = calloc(1, sizeof(struct tcp_server));
	if (!server) {
		log_errno("malloc");
		return -1;
	}

	server->conn_handler = conn_handler;
	server->arg = arg;

	ret = net_bind(port);
	if (ret < 0)
		goto free;
	server->fd = ret;

	*_server = server;
	return ret;

free:
	free(server);

	return ret;
}

void tcp_server_destroy(struct tcp_server *server)
{
	net_close(server->fd);
	free(server);
}

struct child_context {
	int fd;
	tcp_server_conn_handler conn_handler;
	void *arg;
};

static void *connection_thread(void *_ctx)
{
	struct child_context *ctx = (struct child_context *)_ctx;
	int ret = 0;

	/* Let the child thread handle its signals except those that should be
	 * handled in the main thread. */
	ret = signal_block_stops();
	if (ret < 0)
		goto free_ctx;

	ctx->conn_handler(ctx->fd, ctx->arg);

free_ctx:
	free(ctx);

	return NULL;
}

static int create_connection_thread(int fd, tcp_server_conn_handler conn_handler, void *arg)
{
	sigset_t old_mask;
	pthread_t child;
	int ret = 0;

	struct child_context *ctx = calloc(1, sizeof(*ctx));
	if (!ctx) {
		log_errno("calloc");
		return -1;
	}

	ctx->fd = fd;
	ctx->conn_handler = conn_handler;
	ctx->arg = arg;

	/* Block all signals (we'll unblock them later); the child thread will
	 * have all signals blocked initially. This allows the main thread to
	 * handle SIGINT/SIGTERM/etc. */
	ret = signal_block_all(&old_mask);
	if (ret < 0)
		goto free_ctx;

	ret = pthread_create(&child, NULL, connection_thread, ctx);
	if (ret) {
		pthread_errno(ret, "pthread_create");
		goto restore_mask;
	}

restore_mask:
	/* Restore the previously-enabled signals for handling in the main thread. */
	signal_restore(&old_mask);

	return ret;

free_ctx:
	free(ctx);

	return ret;
}

int tcp_server_accept(const struct tcp_server *server)
{
	int fd = -1, ret = 0;

	ret = net_accept(server->fd);
	if (ret < 0)
		return ret;
	fd = ret;

	ret = create_connection_thread(fd, server->conn_handler, server->arg);
	if (ret < 0)
		goto close_conn;

	return ret;

close_conn:
	net_close(fd);

	return ret;
}

static int tcp_server_event_handler(UNUSED struct event_loop *loop, UNUSED int fd,
                                    UNUSED short revents, void *_server)
{
	struct tcp_server *server = (struct tcp_server *)_server;
	return tcp_server_accept(server);
}

int tcp_server_add_to_event_loop(struct tcp_server *server, struct event_loop *loop)
{
	return event_loop_add(loop, server->fd, POLLIN, tcp_server_event_handler, server);
}