Browse code

WIP: Add implementation

Robert Cranston authored on 28/11/2020 18:28:43
Showing 5 changed files

... ...
@@ -5,6 +5,7 @@ PROG = sockchat
5 5
 BUILD ?= debug
6 6
 
7 7
 ## Built-in variable defaults
8
+CFLAGS += -std=c99
8 9
 CFLAGS += -Wall -Wextra -Werror -pedantic
9 10
 
10 11
 ## Standard variable defaults
... ...
@@ -2,6 +2,99 @@
2 2
 
3 3
 Chat with unlimited number of peers through a variety of sockets.
4 4
 
5
+`sockchat` is a simple [client-server][] [online chat][] application with a
6
+[terminal user interface][]. Both the client and the server are bundled into
7
+the same executable. One of the main goals is to support a broad variety of
8
+communication protocols, see [usage](#usage).
9
+
10
+`sockchat` was written for educational purposes under a fairly short time and
11
+consists of less than 600 lines of portable [POSIX 2004][], [C99][] code. Some
12
+of the things explored:
13
+
14
+-   Standard C:
15
+    -   [`errno.h`][], thorough error handling.
16
+    -   [`stdarg.h`][], useful and convenient logging and error reporting.
17
+-   POSIX:
18
+    -   [`getopt`][], robust command line interface.
19
+    -   [`sys/socket.h`][], Berkley sockets.
20
+    -   [`sys/select.h`][], local and network I/O multiplexing.
21
+    -   [`netdb.h`][], network protocols.
22
+    -   [`search.h`][], data structures.
23
+    -   [`termios.h`][], simple text-based user interface.
24
+-   Software design:
25
+    -   [Data-centered design][].
26
+    -   Low [coupling][].
27
+    -   Narrow variable [scope][]s.
28
+    -   79 [characters per line][], consistent breaking strategies.
29
+    -   Horrible, horrible [macros][].
30
+
31
+[Client-server]: https://en.wikipedia.org/wiki/Client-server
32
+[Online chat]: https://en.wikipedia.org/wiki/Online_chat
33
+[Terminal user interface]: https://en.wikipedia.org/wiki/Terminal_user_interface
34
+[POSIX 2004]: https://pubs.opengroup.org/onlinepubs/009695399/toc.htm
35
+[C99]: https://en.wikipedia.org/wiki/C99
36
+[`errno.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
37
+[`stdarg.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdarg.h.html
38
+[`getopt`]: https://pubs.opengroup.org/onlinepubs/009695399/functions/getopt.html
39
+[`sys/socket.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_socket.h.html
40
+[`sys/select.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_select.h.html
41
+[`netdb.h`]: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/netdb.h.html
42
+[`search.h`]: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/search.h.html
43
+[`termios.h`]: https://pubs.opengroup.org/onlinepubs/007904975/basedefs/termios.h.html
44
+[Data-centered design]: https://lwn.net/Articles/193245/
45
+[Coupling]: https://en.wikipedia.org/wiki/Coupling_(computer_programming)
46
+[Scope]: https://en.wikipedia.org/wiki/Scope_(computer_science)
47
+[Characters per line]: https://en.wikipedia.org/wiki/Characters_per_line
48
+[Macros]: https://en.wikipedia.org/wiki/C_preprocessor
49
+
50
+## Usage
51
+
52
+See also [examples](#examples).
53
+
54
+`sockchat --help`:
55
+
56
+```
57
+sockchat 1.0 - Chat with unlimited number of peers through a variety of sockets
58
+
59
+Usage:
60
+  sockchat (server|client) [options]
61
+  sockchat -h|--help
62
+  sockchat --version
63
+
64
+Options:
65
+  -f <family>    [default: INET]
66
+  -t <socktype>  [default: DGRAM]
67
+  -p <protocol>  [default: 0]
68
+  -n <node>      [default: ]
69
+  -s <service>   [default: 3200]
70
+
71
+Recognized values for <family>:
72
+  UNSPEC
73
+  UNIX
74
+  LOCAL
75
+  INET
76
+  INET6
77
+  PACKET
78
+
79
+Recognized values for <socktype>:
80
+  0
81
+  STREAM
82
+  DGRAM
83
+  SEQPACKET
84
+  RAW
85
+  RDM
86
+  PACKET
87
+
88
+Recognized values for <protocol>:
89
+  0
90
+  IP
91
+  TCP
92
+  UDP
93
+  UDPLITE
94
+  SCTP
95
+  ICMP
96
+```
97
+
5 98
 ## Building
6 99
 
7 100
 A simple [`Makefile`](Makefile) is provided, build with
... ...
@@ -12,13 +105,63 @@ make
12 105
 
13 106
 ## Installation
14 107
 
15
-The produced executable is self-contained and can be run from wherever, but can
16
-be installed with
108
+The produced executable can be run from anywhere. It can however be installed
109
+with
17 110
 
18 111
 ```sh
19 112
 make install
20 113
 ```
21 114
 
115
+## Implementation notes
116
+
117
+## Examples
118
+
119
+<table>
120
+<tr>
121
+<th>Server</th>
122
+<th>Client Alice</th>
123
+<th>Client Bob</th>
124
+</tr>
125
+<tr>
126
+<td style="vertical-align: top;">
127
+<pre>
128
+$ sockchat server
129
+Info: Using buffer size 255
130
+Info: Trying to bind...
131
+Info: Succeeded to bind
132
+Info: Client '[192.168.0.3:50453] Alice' joined
133
+Info: Client '[192.168.0.4:48678] Bob' joined
134
+Info: Client '[192.168.0.3:50453] Alice' sent 11 bytes
135
+Info: Client '[192.168.0.4:48678] Bob' sent 13 bytes
136
+Info: Client '[192.168.0.3:50453] Alice' left
137
+Info: Client '[192.168.0.4:48678] Bob' left
138
+</pre>
139
+</td>
140
+<td style="vertical-align: top;">
141
+<pre>
142
+$ sockchat client
143
+Info: Using buffer size 255
144
+Info: Trying to connect...
145
+Info: Succeeded to connect
146
+User name: Alice
147
+[192.168.0.3:50453] Alice: Hello Bob!
148
+[192.168.0.4:48678] Bob: Hello Alice!
149
+</pre>
150
+</td>
151
+<td style="vertical-align: top;">
152
+<pre>
153
+$ sockchat client
154
+Info: Using buffer size 255
155
+Info: Trying to connect...
156
+Info: Succeeded to connect
157
+User name: Bob
158
+[192.168.0.3:50453] Alice: Hello Bob!
159
+[192.168.0.4:48678] Bob: Hello Alice!
160
+</pre>
161
+</td>
162
+</tr>
163
+</table>
164
+
22 165
 ## License
23 166
 
24 167
 Licensed under the [ISC License][], see the [`LICENSE`](LICENSE) file.
25 168
new file mode 100644
... ...
@@ -0,0 +1,123 @@
1
+#include <stdlib.h>
2
+#include <stdio.h>
3
+
4
+
5
+#ifndef ARGS_FORMAT_HEADER
6
+#define ARGS_FORMAT_HEADER       "Recognized values for <%s>:\n"
7
+#endif
8
+#ifndef ARGS_FORMAT_NAME
9
+#define ARGS_FORMAT_NAME         "  %s\n"
10
+#endif
11
+#ifndef ARGS_FORMAT_POSITIONAL
12
+#define ARGS_FORMAT_POSITIONAL   "No %s specified"
13
+#endif
14
+#ifndef ARGS_FORMAT_COLON
15
+#define ARGS_FORMAT_COLON        "Option -%c requires an argument"
16
+#endif
17
+#ifndef ARGS_FORMAT_QUESTIONMARK
18
+#define ARGS_FORMAT_QUESTIONMARK "Unrecognized option: '-%c'"
19
+#endif
20
+#ifndef ARGS_FORMAT_DEFAULT
21
+#define ARGS_FORMAT_DEFAULT      "Failed to parse option: '-%c'"
22
+#endif
23
+#ifndef ARGS_FORMAT_FIND
24
+#define ARGS_FORMAT_FIND         "Unrecognized %s: '%s'"
25
+#endif
26
+#ifndef ARGS_FORMAT_CATCH
27
+#define ARGS_FORMAT_CATCH        "Unrecognized argument: '%s'"
28
+#endif
29
+
30
+#define ARGS_PRINT(STREAM, ARG, ARGS) \
31
+    fprintf(STREAM, ARGS_FORMAT_HEADER, #ARG); \
32
+    for (size_t argi = 0; argi < sizeof(ARGS) / sizeof(*ARGS); ++argi) { \
33
+        void const * arg = &((char const *)ARGS)[argi * sizeof(*ARGS)]; \
34
+        fprintf(STREAM, ARGS_FORMAT_NAME, *(char const **)arg); \
35
+    }
36
+
37
+#define ARGS_SPECIALS() \
38
+    if (argc > 1) { \
39
+        if ( \
40
+            0 == strcmp(argv[1], "-h") || \
41
+            0 == strcmp(argv[1], "--help") \
42
+        ) { \
43
+            help(stdout); \
44
+            exit(EXIT_SUCCESS); \
45
+        } \
46
+        if ( \
47
+            0 == strcmp(argv[1], "--version") \
48
+        ) { \
49
+            version(stdout); \
50
+            exit(EXIT_SUCCESS); \
51
+        } \
52
+    }
53
+
54
+#define ARGS_DECLARE(ARG, DEFAULT) \
55
+    char const * arg_##ARG = DEFAULT;
56
+
57
+#define ARGS_POSITIONAL(ARG) \
58
+    if (optind >= argc) { \
59
+        fprintf(stderr, ARGS_FORMAT_POSITIONAL "\n\n", #ARG); \
60
+        usage(stderr); \
61
+        exit(EXIT_FAILURE); \
62
+    } \
63
+    arg_##ARG = argv[optind++];
64
+
65
+#define ARGS_OPTIONS_BEGIN(OPTSTRING) \
66
+    { \
67
+        int opt; \
68
+        int opterr = 0; \
69
+        while (-1 != (opt = getopt(argc, argv, ":" OPTSTRING))) { \
70
+            switch(opt) {
71
+
72
+#define ARGS_OPTION(OPT, ARG) \
73
+                case OPT: arg_##ARG = argv[optind]; break;
74
+
75
+#define ARGS_OPTION_WITH_ARGUMENT(OPT, ARG) \
76
+                case OPT: arg_##ARG = optarg; break;
77
+
78
+#define ARGS_OPTIONS_END() \
79
+                case ':': \
80
+                    fprintf(stderr, ARGS_FORMAT_COLON "\n\n", optopt); \
81
+                    opterr = 1; \
82
+                    break; \
83
+                case '?': \
84
+                    fprintf(stderr, ARGS_FORMAT_QUESTIONMARK "\n\n", optopt); \
85
+                    opterr = 1; \
86
+                    break; \
87
+                default: \
88
+                    fprintf(stderr, ARGS_FORMAT_DEFAULT "\n\n", opt); \
89
+                    opterr = 1; \
90
+                    break; \
91
+            } \
92
+        } \
93
+        if (opterr) { \
94
+            usage(stderr); \
95
+            exit(EXIT_FAILURE); \
96
+        } \
97
+    }
98
+
99
+#define ARGS_CONVERT_BOOL(ARG) \
100
+    int ARG = !!arg_##ARG;
101
+
102
+#define ARGS_CONVERT_NULL(ARG) \
103
+    char const * ARG = *(arg_##ARG) ? (arg_##ARG) : NULL;
104
+
105
+#define ARGS_CONVERT_FIND(TYPE, ARG, ARGS) \
106
+    struct TYPE const * ARG = NULL; \
107
+    for (size_t argi = 0; argi < sizeof(ARGS) / sizeof(*ARGS); ++argi) { \
108
+        void const * arg = &((char const *)ARGS)[argi * sizeof(*ARGS)]; \
109
+        if (0 == strcmp(arg_##ARG, *(char const **)arg)) \
110
+            ARG = arg; \
111
+    } \
112
+    if (!ARG) { \
113
+        fprintf(stderr, ARGS_FORMAT_FIND "\n\n", #ARG, arg_##ARG); \
114
+        usage(stderr); \
115
+        exit(EXIT_FAILURE); \
116
+    } \
117
+
118
+#define ARGS_CATCH() \
119
+    if (optind < argc) { \
120
+        fprintf(stderr, ARGS_FORMAT_CATCH "\n\n", argv[optind]); \
121
+        usage(stderr); \
122
+        exit(EXIT_FAILURE); \
123
+    }
0 124
new file mode 100644
... ...
@@ -0,0 +1,58 @@
1
+// TODO: Document that `perror(3)` is standard but does not take a printf-style
2
+// format argument. The ones that do are nonstandard, `err(3)` (BSD), `error`
3
+// (GNU).
4
+#include <stdlib.h>
5
+#include <stdio.h>
6
+#include <stdarg.h>
7
+#include <errno.h>
8
+
9
+
10
+#define REPORT_INFO  0
11
+#define REPORT_ERROR 1
12
+#define REPORT_FATAL 2
13
+
14
+#define report_info( ...) report(REPORT_INFO,  __VA_ARGS__)
15
+#define report_error(...) report(REPORT_ERROR, __VA_ARGS__)
16
+#define report_fatal(...) report(REPORT_FATAL, __VA_ARGS__)
17
+
18
+#ifndef REPORT_STREAM_INFO
19
+#define REPORT_STREAM_INFO  stderr
20
+#endif
21
+#ifndef REPORT_STREAM_ERROR
22
+#define REPORT_STREAM_ERROR stderr
23
+#endif
24
+
25
+#ifndef REPORT_FORMAT_PREFIX_INFO
26
+#define REPORT_FORMAT_PREFIX_INFO  "Info: "
27
+#endif
28
+#ifndef REPORT_FORMAT_PREFIX_ERROR
29
+#define REPORT_FORMAT_PREFIX_ERROR "Error: "
30
+#endif
31
+#ifndef REPORT_FORMAT_PREFIX_FATAL
32
+#define REPORT_FORMAT_PREFIX_FATAL "Fatal: "
33
+#endif
34
+
35
+
36
+static int report(int report, int report_errno, char const * format, ...) {
37
+    FILE * stream = REPORT_STREAM_INFO;
38
+    if (report >= REPORT_ERROR)
39
+        stream = REPORT_STREAM_ERROR;
40
+    fprintf(stream, "%s",
41
+        report == REPORT_INFO  ? REPORT_FORMAT_PREFIX_INFO  :
42
+        report == REPORT_ERROR ? REPORT_FORMAT_PREFIX_ERROR :
43
+        report == REPORT_FATAL ? REPORT_FORMAT_PREFIX_FATAL :
44
+        ""
45
+    );
46
+    {
47
+        va_list ap;
48
+        va_start(ap, format);
49
+        vfprintf(stream, format, ap);
50
+        va_end(ap);
51
+    }
52
+    if (report_errno)
53
+        fprintf(stream, ": %s", strerror(report_errno));
54
+    fprintf(stream, "\n");
55
+    if (report >= REPORT_FATAL)
56
+        exit(EXIT_FAILURE);
57
+    return report;
58
+}
0 59
new file mode 100644
... ...
@@ -0,0 +1,538 @@
1
+// TODO: Add `listen` and `accept` for if the socket has a `STREAM` `socktype`.
2
+// TODO: Compare to the example in `getaddrinfo(3)`.
3
+// TODO: Mention `getaddrinfo` and the RFC 3484 sorting order in readme.
4
+// TODO: Change client store from queue to binary tree?
5
+
6
+/// Headers
7
+
8
+//// POSIX 2004
9
+#define _XOPEN_SOURCE 600
10
+
11
+//// C standard library
12
+#include <stdlib.h>
13
+#include <stdio.h>
14
+#include <string.h>
15
+#include <ctype.h>
16
+#include <limits.h>
17
+
18
+//// POSIX
19
+#include <unistd.h>
20
+#include <sys/types.h>
21
+#include <sys/socket.h>
22
+#include <sys/select.h>
23
+#include <netdb.h>
24
+#include <search.h>
25
+#include <termios.h>
26
+
27
+// Other
28
+#include "args.h"
29
+#include "report.h"
30
+
31
+
32
+/// Parameters
33
+
34
+//// Application
35
+#define PROGNAME "sockchat"
36
+#define VERSION_STR "1.0"
37
+#define DESCRIPTION \
38
+    "Chat with unlimited number of peers through a variety of sockets"
39
+
40
+//// Arguments
41
+// TODO
42
+// #define DEFAULT_FAMILY   "UNSPEC"
43
+// #define DEFAULT_SOCKTYPE "0"
44
+#define DEFAULT_FAMILY   "INET"
45
+#define DEFAULT_SOCKTYPE "DGRAM"
46
+#define DEFAULT_PROTOCOL "0"
47
+#define DEFAULT_NODE     ""
48
+#define DEFAULT_SERVICE  "3200"
49
+
50
+//// Command line interface
51
+#define VERSION \
52
+    PROGNAME " " VERSION_STR " - " DESCRIPTION "\n"
53
+#define USAGE \
54
+    "Usage:\n" \
55
+    "  " PROGNAME " (server|client) [options]\n" \
56
+    "  " PROGNAME " -h|--help\n" \
57
+    "  " PROGNAME " --version\n"
58
+#define OPTIONS \
59
+    "Options:\n" \
60
+    "  -f <family>    [default: " DEFAULT_FAMILY   "]\n" \
61
+    "  -t <socktype>  [default: " DEFAULT_SOCKTYPE "]\n" \
62
+    "  -p <protocol>  [default: " DEFAULT_PROTOCOL "]\n" \
63
+    "  -n <node>      [default: " DEFAULT_NODE     "]\n" \
64
+    "  -s <service>   [default: " DEFAULT_SERVICE  "]\n"
65
+
66
+//// Implementation
67
+#define BUF_SIZE _POSIX_MAX_CANON
68
+
69
+
70
+/// Forward declarations
71
+
72
+//// Implementation
73
+int getsockfd(
74
+    char const * action_name,
75
+    int (*action)(int sockfd, struct sockaddr const * addr, socklen_t addrlen),
76
+    int flags,
77
+    int family,
78
+    int socktype,
79
+    int protocol,
80
+    char const * node,
81
+    char const * service
82
+);
83
+void run_server(int sockfd);
84
+void run_client(int sockfd);
85
+
86
+
87
+/// Data
88
+
89
+//// Arguments
90
+struct role {
91
+    char const * name;
92
+    char const * action_name;
93
+    int (*action)(int sockfd, struct sockaddr const * addr, socklen_t addrlen);
94
+    int flags;
95
+    void (*run)(int sockfd);
96
+};
97
+struct option {
98
+    char const * name;
99
+    int value;
100
+};
101
+#define ROLE(NAME, ACTION, FLAGS) { #NAME, #ACTION, ACTION, FLAGS, run_##NAME }
102
+static struct role roles[] = {
103
+    ROLE(server, bind, AI_PASSIVE),
104
+    ROLE(client, connect, 0),
105
+};
106
+#define OPTION_FAMILY(FAMILY) { #FAMILY, AF_##FAMILY }
107
+static struct option families[] = {
108
+    OPTION_FAMILY(UNSPEC),
109
+    OPTION_FAMILY(UNIX),
110
+    OPTION_FAMILY(LOCAL),
111
+    OPTION_FAMILY(INET),
112
+    OPTION_FAMILY(INET6),
113
+    OPTION_FAMILY(PACKET),
114
+};
115
+#define OPTION_SOCKTYPE(SOCKTYPE) { #SOCKTYPE, SOCK_##SOCKTYPE }
116
+static struct option socktypes[] = {
117
+    { "0", 0 },
118
+    OPTION_SOCKTYPE(STREAM),
119
+    OPTION_SOCKTYPE(DGRAM),
120
+    OPTION_SOCKTYPE(SEQPACKET),
121
+    OPTION_SOCKTYPE(RAW),
122
+    OPTION_SOCKTYPE(RDM),
123
+    OPTION_SOCKTYPE(PACKET),
124
+};
125
+#define OPTION_PROTOCOL(PROTOCOL) { #PROTOCOL, IPPROTO_##PROTOCOL }
126
+static struct option protocols[] = {
127
+    { "0", 0 },
128
+    OPTION_PROTOCOL(IP),
129
+    OPTION_PROTOCOL(TCP),
130
+    OPTION_PROTOCOL(UDP),
131
+    OPTION_PROTOCOL(UDPLITE),
132
+    OPTION_PROTOCOL(SCTP),
133
+    OPTION_PROTOCOL(ICMP),
134
+};
135
+
136
+
137
+/// Command line interface
138
+void version(FILE * stream) { fprintf(stream, "%s", VERSION); }
139
+void usage  (FILE * stream) { fprintf(stream, "%s", USAGE);   }
140
+void options(FILE * stream) { fprintf(stream, "%s", OPTIONS); }
141
+void args   (FILE * stream) {
142
+    ARGS_PRINT(stream, family,   families ); fprintf(stream, "\n");
143
+    ARGS_PRINT(stream, socktype, socktypes); fprintf(stream, "\n");
144
+    ARGS_PRINT(stream, protocol, protocols);
145
+}
146
+void help(FILE * stream) {
147
+    version(stream); fprintf(stream, "\n");
148
+    usage  (stream); fprintf(stream, "\n");
149
+    options(stream); fprintf(stream, "\n");
150
+    args   (stream);
151
+}
152
+
153
+
154
+/// main
155
+int main(int argc, char * argv[]) {
156
+    // Command line interface
157
+    ARGS_SPECIALS()
158
+    ARGS_DECLARE(role,     NULL)
159
+    ARGS_DECLARE(family,   DEFAULT_FAMILY)
160
+    ARGS_DECLARE(socktype, DEFAULT_SOCKTYPE)
161
+    ARGS_DECLARE(protocol, DEFAULT_PROTOCOL)
162
+    ARGS_DECLARE(node,     DEFAULT_NODE)
163
+    ARGS_DECLARE(service,  DEFAULT_SERVICE)
164
+    ARGS_POSITIONAL(role)
165
+    ARGS_OPTIONS_BEGIN("f:t:p:n:s:")
166
+    ARGS_OPTION_WITH_ARGUMENT('f', family)
167
+    ARGS_OPTION_WITH_ARGUMENT('t', socktype)
168
+    ARGS_OPTION_WITH_ARGUMENT('p', protocol)
169
+    ARGS_OPTION_WITH_ARGUMENT('n', node)
170
+    ARGS_OPTION_WITH_ARGUMENT('s', service)
171
+    ARGS_OPTIONS_END()
172
+    ARGS_CONVERT_FIND(role,   role,     roles)
173
+    ARGS_CONVERT_FIND(option, family,   families)
174
+    ARGS_CONVERT_FIND(option, socktype, socktypes)
175
+    ARGS_CONVERT_FIND(option, protocol, protocols)
176
+    ARGS_CONVERT_NULL(node)
177
+    ARGS_CONVERT_NULL(service)
178
+    ARGS_CATCH()
179
+
180
+    // Implementation
181
+    report_info(0, "Using buffer size %d", BUF_SIZE);
182
+    role->run(getsockfd(
183
+        role->action_name,
184
+        role->action,
185
+        role->flags,
186
+        family->value,
187
+        socktype->value,
188
+        protocol->value,
189
+        node,
190
+        service
191
+    ));
192
+}
193
+
194
+
195
+/// Implementation
196
+
197
+//// getsockfd
198
+int getsockfd(
199
+    char const * action_name,
200
+    int (*action)(int sockfd, struct sockaddr const * addr, socklen_t addrlen),
201
+    int flags,
202
+    int family,
203
+    int socktype,
204
+    int protocol,
205
+    char const * node,
206
+    char const * service
207
+) {
208
+    int gai_errno;
209
+    int sockfd;
210
+    {
211
+        struct addrinfo * addrinfos;
212
+        // Get addresses.
213
+        {
214
+            // Populate hints.
215
+            // The extra flags are assumed by the GNU C library if no hints are
216
+            // given (in contradiction to POSIX), and are a good idea.
217
+            struct addrinfo hints;
218
+            memset(&hints, 0, sizeof(hints));
219
+            hints.ai_flags = flags | AI_V4MAPPED | AI_ADDRCONFIG;
220
+            hints.ai_family = family;
221
+            hints.ai_socktype = socktype;
222
+            hints.ai_protocol = protocol;
223
+            // Query.
224
+            if (0 != (gai_errno = getaddrinfo(
225
+                node, service, &hints, &addrinfos
226
+            )))
227
+                report_fatal(gai_errno == EAI_SYSTEM ? errno : 0,
228
+                    "Failed to get addresses for '%s:%s': %s",
229
+                    node, service, gai_strerror(gai_errno)
230
+                );
231
+        }
232
+        // Try action on addresses until one works.
233
+        report_info(0, "Trying to %s...", action_name);
234
+        struct addrinfo * addrinfo;
235
+        for (
236
+            addrinfo = addrinfos;
237
+            addrinfo != NULL;
238
+            addrinfo = addrinfo->ai_next
239
+        ) {
240
+            // Create socket.
241
+            if (-1 == (sockfd = socket(
242
+                addrinfo->ai_family,
243
+                addrinfo->ai_socktype,
244
+                addrinfo->ai_protocol
245
+            ))) {
246
+                report_info(errno, "> Failed to create socket");
247
+                continue;
248
+            }
249
+            // Perform action.
250
+            if (-1 == action(
251
+                sockfd,
252
+                addrinfo->ai_addr,
253
+                addrinfo->ai_addrlen
254
+            )) {
255
+                close(sockfd);
256
+                report_info(errno, "> Failed to %s", action_name);
257
+                continue;
258
+            }
259
+            // Succeeded.
260
+            report_info(0, "Succeeded to %s", action_name);
261
+            break;
262
+        }
263
+        if (NULL == addrinfo)
264
+            report_fatal(0, "Failed to get socket");
265
+        // Clean up.
266
+        freeaddrinfo(addrinfos);
267
+    }
268
+    return sockfd;
269
+}
270
+
271
+
272
+//// run_server
273
+void run_server(int sockfd) {
274
+    int gai_errno;
275
+
276
+    // Client data.
277
+    struct client_data {
278
+        char * name;
279
+    };
280
+
281
+    // Client accounting.
282
+    struct client {
283
+        struct client * next;
284
+        struct client * prev;
285
+        struct sockaddr_storage addr;
286
+        struct client_data data;
287
+    };
288
+    struct client * clients = NULL;
289
+
290
+    // Allocate buffer.
291
+    int buflen;
292
+    char * buf = malloc(BUF_SIZE);
293
+    if (!buf)
294
+        report_fatal(errno, "Failed to allocate buffer of size %d", BUF_SIZE);
295
+
296
+    // Talk to clients.
297
+    while (1) {
298
+        // Peek receive length and address.
299
+        int recvlen;
300
+        struct sockaddr_storage addr;
301
+        socklen_t addrlen = sizeof(addr);
302
+        if (-1 == (recvlen = recvfrom(
303
+            sockfd,
304
+            buf,
305
+            BUF_SIZE - 1,
306
+            MSG_PEEK,
307
+            (struct sockaddr *)&addr,
308
+            &addrlen
309
+        )))
310
+            report_fatal(errno, "Failed to peek receive");
311
+        // Check for too large addresses (guaranteed not to happen).
312
+        if (addrlen > sizeof(addr))
313
+            report_fatal(0,
314
+                "Failed to store address of size %d, can only hold %d",
315
+                addrlen, sizeof(addr)
316
+            );
317
+        // Look up client.
318
+        struct client * client;
319
+        for (
320
+            client = clients;
321
+            client != NULL;
322
+            client = client->next
323
+        )
324
+            if (0 == memcmp(&client->addr, &addr, sizeof(addr)))
325
+                break;
326
+        // Client left.
327
+        if (!recvlen) {
328
+            if (-1 == recv(sockfd, NULL, 0, 0))
329
+                report_fatal(errno, "Failed to receive leave");
330
+            if (client) {
331
+                // Data.
332
+                report_info(0, "Client '%s' left", client->data.name);
333
+                free(client->data.name);
334
+                // Accounting.
335
+                remque(client);
336
+                free(client);
337
+            }
338
+            continue;
339
+        }
340
+        // Client joined.
341
+        if (!client) {
342
+            // Accounting.
343
+            if (NULL == (client = calloc(1, sizeof(*client))))
344
+                report_fatal(errno, "Failed allocate client of size %d",
345
+                    sizeof(*client)
346
+                );
347
+            insque(client, &clients);
348
+            client->addr = addr;
349
+            // Data.
350
+            buflen = 0;
351
+            // Add open delimiter.
352
+            buflen += snprintf(&buf[buflen], BUF_SIZE - buflen - 1, "[");
353
+            // Get client address node name.
354
+            if (0 != (gai_errno = getnameinfo(
355
+                (struct sockaddr *)&addr,
356
+                addrlen,
357
+                &buf[buflen],
358
+                BUF_SIZE - buflen - 1,
359
+                NULL,
360
+                0,
361
+                0
362
+            )))
363
+                report_error(gai_errno == EAI_SYSTEM ? errno : 0,
364
+                    "Failed to get client address node name: %s",
365
+                    gai_strerror(gai_errno)
366
+                );
367
+            buflen += strlen(&buf[buflen]);
368
+            // Add separator.
369
+            buflen += snprintf(&buf[buflen], BUF_SIZE - buflen - 1, ":");
370
+            // Get client address service name.
371
+            if (0 != (gai_errno = getnameinfo(
372
+                (struct sockaddr *)&addr,
373
+                addrlen,
374
+                NULL,
375
+                0,
376
+                &buf[buflen],
377
+                BUF_SIZE - buflen - 1,
378
+                0
379
+            )))
380
+                report_error(gai_errno == EAI_SYSTEM ? errno : 0,
381
+                    "Failed to get client address service name: %s",
382
+                    gai_strerror(gai_errno)
383
+                );
384
+            buflen += strlen(&buf[buflen]);
385
+            // Add close delimiter.
386
+            buflen += snprintf(&buf[buflen], BUF_SIZE - buflen - 1, "] ");
387
+            // Get client user name.
388
+            if (-1 == (recvlen = recv(
389
+                sockfd, &buf[buflen], BUF_SIZE - buflen - 1, 0
390
+            )))
391
+                report_fatal(errno, "Failed to receive client user name");
392
+            buflen += recvlen;
393
+            buflen -= buflen && buf[buflen - 1] == '\0';
394
+            // Store.
395
+            buf[buflen++] = '\0';
396
+            client->data.name = strdup(buf);
397
+            report_info(0, "Client '%s' joined", client->data.name);
398
+            continue;
399
+        }
400
+        // Client sent message.
401
+        report_info(0, "Client '%s' sent %d byte%s",
402
+            client->data.name, recvlen, recvlen == 1 ? "" : "s"
403
+        );
404
+        buflen = 0;
405
+        // Add client user name and separator.
406
+        buflen += snprintf(
407
+            &buf[buflen], BUF_SIZE - buflen - 1, "%s: ", client->data.name
408
+        );
409
+        // Add message.
410
+        if (-1 == (recvlen = recv(
411
+            sockfd, &buf[buflen], BUF_SIZE - buflen - 1, 0
412
+        )))
413
+            report_fatal(errno, "Failed to receive");
414
+        buflen += recvlen;
415
+        buflen -= buflen && buf[buflen - 1] == '\0';
416
+        // Send message to all clients.
417
+        buf[buflen++] = '\0';
418
+        for (
419
+            client = clients;
420
+            client != NULL;
421
+            client = client->next
422
+        ) {
423
+            if (-1 == sendto(
424
+                sockfd,
425
+                buf,
426
+                buflen,
427
+                0,
428
+                (struct sockaddr *)&client->addr,
429
+                sizeof(client->addr)
430
+            ))
431
+                report_fatal(errno, "Failed to send");
432
+        }
433
+    }
434
+}
435
+
436
+
437
+//// run_client
438
+void run_client(int sockfd) {
439
+    // Allocate socket buffer.
440
+    int sockbuflen;
441
+    char * sockbuf = malloc(BUF_SIZE);
442
+    if (!sockbuf)
443
+        report_fatal(errno, "Failed to allocate socket buffer of size %d",
444
+            BUF_SIZE
445
+        );
446
+
447
+    // Allocate input buffer.
448
+    int inputbuflen;
449
+    char * inputbuf = malloc(BUF_SIZE);
450
+    if (!inputbuf)
451
+        report_fatal(errno, "Failed to allocate input buffer of size %d",
452
+            BUF_SIZE
453
+        );
454
+
455
+    // Join.
456
+    // Get user name.
457
+    printf("User name: ");
458
+    fflush(stdout);
459
+    if (-1 == (inputbuflen = read(STDIN_FILENO, inputbuf, BUF_SIZE - 1)))
460
+        report_fatal(errno, "Failed to read user name");
461
+    inputbuflen -= inputbuflen && inputbuf[inputbuflen - 1] == '\n';
462
+    // Send join.
463
+    inputbuf[inputbuflen++] = '\0';
464
+    if (-1 == send(sockfd, inputbuf, inputbuflen, 0))
465
+        report_fatal(errno, "Failed to send join");
466
+
467
+    // Setup terminal.
468
+    struct termios termios_stdin;
469
+    if (-1 == tcgetattr(STDIN_FILENO, &termios_stdin))
470
+        report_fatal(errno, "Failed to get terminal settings");
471
+    {
472
+        struct termios termios = termios_stdin;
473
+        termios.c_lflag &= ~(ICANON | ECHO);
474
+        if (-1 == tcsetattr(STDIN_FILENO, TCSANOW, &termios))
475
+            report_fatal(errno, "Failed to set terminal settings");
476
+    }
477
+
478
+    // Talk to user and server.
479
+    inputbuflen = 0;
480
+    while (1) {
481
+        // Wait for data to be available from input or socket.
482
+        fd_set rfds;
483
+        FD_ZERO(&rfds);
484
+        FD_SET(STDIN_FILENO, &rfds);
485
+        FD_SET(sockfd, &rfds);
486
+        if (-1 == select(sockfd+1, &rfds, NULL, NULL, NULL)) {
487
+            printf("\n");
488
+            report_fatal(errno, "Failed to wait for data");
489
+        }
490
+        // Clear input line.
491
+        printf("\r%*s\r", inputbuflen, "");
492
+        // Socket data available.
493
+        if (FD_ISSET(sockfd, &rfds)) {
494
+            // Receive.
495
+            if (-1 == (sockbuflen = recv(sockfd, sockbuf, BUF_SIZE - 1, 0)))
496
+                report_fatal(errno, "Failed to receive");
497
+            sockbuf[sockbuflen++] = '\0';
498
+            // Print socket line.
499
+            printf("%s\n", sockbuf);
500
+        }
501
+        // Input data available.
502
+        if (FD_ISSET(STDIN_FILENO, &rfds)) {
503
+            // Read.
504
+            char c;
505
+            if (-1 == read(STDIN_FILENO, &c, 1))
506
+                report_fatal(errno, "Failed to read input");
507
+            // Printable with non-full buffer.
508
+            if (isprint(c) && inputbuflen < BUF_SIZE - 1) {
509
+                // Add.
510
+                inputbuf[inputbuflen++] = c;
511
+            // Backspace.
512
+            } else if (c == '\b' || c == 127) {
513
+                // Remove last character from buffer.
514
+                if (inputbuflen)
515
+                    --inputbuflen;
516
+            // Enter.
517
+            } else if (c == '\n' || c == '\r') {
518
+                // Send.
519
+                if (inputbuflen)
520
+                    inputbuf[inputbuflen++] = '\0';
521
+                if (-1 == send(sockfd, inputbuf, inputbuflen, 0))
522
+                    report_fatal(errno, "Failed to send");
523
+                // Quit if input line empty.
524
+                if (!inputbuflen)
525
+                    break;
526
+                // Reset input line.
527
+                inputbuflen = 0;
528
+            }
529
+        }
530
+        // Print input line.
531
+        printf("%.*s", inputbuflen, inputbuf);
532
+        fflush(stdout);
533
+    }
534
+
535
+    // Restore terminal.
536
+    if (-1 == tcsetattr(STDIN_FILENO, TCSANOW, &termios_stdin))
537
+        report_fatal(errno, "Failed to restore terminal settings");
538
+}