Browse code

WIP: Add implementation

Robert Cranston authored on 06/02/2021 04:14:21
Showing 6 changed files

... ...
@@ -1,12 +1,18 @@
1 1
 ## Project specific variables
2 2
 PROG = sockchat
3
+CSTD = c99
3 4
 
4 5
 ## Custom variable defaults
5 6
 BUILD ?= debug
6 7
 
7 8
 ## Built-in variable defaults
8
-CFLAGS += -std=c99
9
-CFLAGS += -Wall -Wextra -Werror -pedantic
9
+WFLAGS += -Werror
10
+WFLAGS += -Wpedantic -Wall -Wextra
11
+WFLAGS += -Wconversion -Wno-sign-conversion
12
+WFLAGS += -Wshadow
13
+WFLAGS += -Wimplicit-fallthrough
14
+WFLAGS += -Wvla
15
+CFLAGS += $(WFLAGS) -std=$(CSTD)
10 16
 
11 17
 ## Standard variable defaults
12 18
 PREFIX ?= /usr/local
... ...
@@ -17,6 +23,7 @@ INSTALL_PROGRAM ?= $(INSTALL)
17 23
 ## Build type
18 24
 ifeq ($(BUILD),debug)
19 25
     CFLAGS += -g
26
+    CFLAGS += -fsanitize=signed-integer-overflow
20 27
 else ifeq ($(BUILD),release)
21 28
     CFLAGS += -DNDEBUG -O3
22 29
     LDFLAGS += -s
... ...
@@ -31,16 +31,16 @@ of the things explored:
31 31
 [Client-server]: https://en.wikipedia.org/wiki/Client-server
32 32
 [Online chat]: https://en.wikipedia.org/wiki/Online_chat
33 33
 [Terminal user interface]: https://en.wikipedia.org/wiki/Terminal_user_interface
34
-[POSIX 2004]: https://pubs.opengroup.org/onlinepubs/009695399/toc.htm
34
+[POSIX 2004]: https://pubs.opengroup.org/onlinepubs/9699919799/toc.htm
35 35
 [C99]: https://en.wikipedia.org/wiki/C99
36 36
 [`errno.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
37 37
 [`stdarg.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdarg.h.html
38
-[`getopt`]: https://pubs.opengroup.org/onlinepubs/009695399/functions/getopt.html
38
+[`getopt`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getopt.html
39 39
 [`sys/socket.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_socket.h.html
40 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
41
+[`netdb.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/netdb.h.html
42
+[`search.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/search.h.html
43
+[`termios.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/termios.h.html
44 44
 [Data-centered design]: https://lwn.net/Articles/193245/
45 45
 [Coupling]: https://en.wikipedia.org/wiki/Coupling_(computer_programming)
46 46
 [Scope]: https://en.wikipedia.org/wiki/Scope_(computer_science)
47 47
new file mode 100644
... ...
@@ -0,0 +1,152 @@
1
+#include <stdlib.h>
2
+#include <stdio.h>
3
+
4
+
5
+struct arg_const {
6
+    char const * name;
7
+    int const value;
8
+};
9
+
10
+
11
+#ifndef ARG_HELP
12
+#define ARG_HELP help
13
+#endif
14
+#ifndef ARG_VERSION
15
+#define ARG_VERSION version
16
+#endif
17
+#ifndef ARG_USAGE
18
+#define ARG_USAGE usage
19
+#endif
20
+#ifndef ARG_FORMAT_HEADER
21
+#define ARG_FORMAT_HEADER       "Recognized values for <%s>:\n"
22
+#endif
23
+#ifndef ARG_FORMAT_NAME
24
+#define ARG_FORMAT_NAME         "  %s\n"
25
+#endif
26
+#ifndef ARG_FORMAT_POSITIONAL
27
+#define ARG_FORMAT_POSITIONAL   "No %s specified"
28
+#endif
29
+#ifndef ARG_FORMAT_COLON
30
+#define ARG_FORMAT_COLON        "Option -%c requires an argument"
31
+#endif
32
+#ifndef ARG_FORMAT_QUESTIONMARK
33
+#define ARG_FORMAT_QUESTIONMARK "Unrecognized option: '-%c'"
34
+#endif
35
+#ifndef ARG_FORMAT_DEFAULT
36
+#define ARG_FORMAT_DEFAULT      "Failed to parse option: '-%c'"
37
+#endif
38
+#ifndef ARG_FORMAT_FIND
39
+#define ARG_FORMAT_FIND         "Unrecognized %s: '%s'"
40
+#endif
41
+#ifndef ARG_FORMAT_CATCH
42
+#define ARG_FORMAT_CATCH        "Unrecognized argument: '%s'"
43
+#endif
44
+
45
+#define ARG_PRINT(STREAM, ARG, ARGS) \
46
+    fprintf(STREAM, ARG_FORMAT_HEADER, #ARG); \
47
+    for (size_t argi = 0; argi < sizeof(ARGS) / sizeof(*ARGS); ++argi) { \
48
+        void const * arg = &((char const *)ARGS)[argi * sizeof(*ARGS)]; \
49
+        fprintf(STREAM, ARG_FORMAT_NAME, *(char const **)arg); \
50
+    }
51
+
52
+#define ARG_SPECIALS() \
53
+    if (argc > 1) { \
54
+        if ( \
55
+            0 == strcmp(argv[1], "-h") || \
56
+            0 == strcmp(argv[1], "--help") \
57
+        ) { \
58
+            ARG_HELP(stdout); \
59
+            exit(EXIT_SUCCESS); \
60
+        } \
61
+        if ( \
62
+            0 == strcmp(argv[1], "--version") \
63
+        ) { \
64
+            ARG_VERSION(stdout); \
65
+            exit(EXIT_SUCCESS); \
66
+        } \
67
+    }
68
+
69
+#define ARG_DECLARE(ARG, DEFAULT) \
70
+    char const * arg_##ARG = DEFAULT;
71
+
72
+#define ARG_POSITIONAL(ARG) \
73
+    if (optind >= argc) { \
74
+        fprintf(stderr, ARG_FORMAT_POSITIONAL "\n\n", #ARG); \
75
+        ARG_USAGE(stderr); \
76
+        exit(EXIT_FAILURE); \
77
+    } \
78
+    arg_##ARG = argv[optind++];
79
+
80
+#define ARG_GETOPT_BEGIN(OPTSTRING) \
81
+    { \
82
+        int opt; \
83
+        int opterr = 0; \
84
+        while (-1 != (opt = getopt(argc, argv, ":" OPTSTRING))) { \
85
+            switch(opt) {
86
+
87
+#define ARG_GETOPT(OPT, ARG) \
88
+                case OPT: arg_##ARG = argv[optind]; break;
89
+
90
+#define ARG_GETOPT_WITH_ARGUMENT(OPT, ARG) \
91
+                case OPT: arg_##ARG = optarg; break;
92
+
93
+#define ARG_GETOPT_END() \
94
+                case ':': \
95
+                    fprintf(stderr, ARG_FORMAT_COLON "\n\n", optopt); \
96
+                    opterr = 1; \
97
+                    break; \
98
+                case '?': \
99
+                    fprintf(stderr, ARG_FORMAT_QUESTIONMARK "\n\n", optopt); \
100
+                    opterr = 1; \
101
+                    break; \
102
+                default: \
103
+                    fprintf(stderr, ARG_FORMAT_DEFAULT "\n\n", opt); \
104
+                    opterr = 1; \
105
+                    break; \
106
+            } \
107
+        } \
108
+        if (opterr) { \
109
+            ARG_USAGE(stderr); \
110
+            exit(EXIT_FAILURE); \
111
+        } \
112
+    }
113
+
114
+#define ARG_CONVERT_BOOL(ARG) \
115
+    int ARG = !!arg_##ARG;
116
+
117
+#define ARG_CONVERT_NULL(ARG) \
118
+    char const * ARG = *(arg_##ARG) ? (arg_##ARG) : NULL;
119
+
120
+#define ARG_CONVERT_FIND(TYPE, ARG, ARGS) \
121
+    struct TYPE const * ARG = NULL; \
122
+    for (size_t argi = 0; argi < sizeof(ARGS) / sizeof(*ARGS); ++argi) { \
123
+        void const * arg = &((char const *)ARGS)[argi * sizeof(*ARGS)]; \
124
+        if (0 == strcmp(arg_##ARG, *(char const **)arg)) \
125
+            ARG = arg; \
126
+    } \
127
+    if (!ARG) { \
128
+        fprintf(stderr, ARG_FORMAT_FIND "\n\n", #ARG, arg_##ARG); \
129
+        ARG_USAGE(stderr); \
130
+        exit(EXIT_FAILURE); \
131
+    } \
132
+
133
+#define ARG_CONVERT_FIND_CONST(ARG, ARGS) \
134
+    struct arg_const const * arg_const_##ARG = NULL; \
135
+    for (size_t argi = 0; argi < sizeof(ARGS) / sizeof(*ARGS); ++argi) { \
136
+        void const * arg = &((char const *)ARGS)[argi * sizeof(*ARGS)]; \
137
+        if (0 == strcmp(arg_##ARG, *(char const **)arg)) \
138
+            arg_const_##ARG = arg; \
139
+    } \
140
+    if (!arg_const_##ARG) { \
141
+        fprintf(stderr, ARG_FORMAT_FIND "\n\n", #ARG, arg_##ARG); \
142
+        ARG_USAGE(stderr); \
143
+        exit(EXIT_FAILURE); \
144
+    } \
145
+    int ARG = arg_const_##ARG->value; \
146
+
147
+#define ARG_CATCH() \
148
+    if (optind < argc) { \
149
+        fprintf(stderr, ARG_FORMAT_CATCH "\n\n", argv[optind]); \
150
+        ARG_USAGE(stderr); \
151
+        exit(EXIT_FAILURE); \
152
+    }
0 153
deleted file mode 100644
... ...
@@ -1,123 +0,0 @@
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
-    }
... ...
@@ -11,38 +11,40 @@
11 11
 #define REPORT_ERROR 1
12 12
 #define REPORT_FATAL 2
13 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 14
 #ifndef REPORT_STREAM_INFO
19 15
 #define REPORT_STREAM_INFO  stderr
20 16
 #endif
21 17
 #ifndef REPORT_STREAM_ERROR
22 18
 #define REPORT_STREAM_ERROR stderr
23 19
 #endif
20
+#ifndef REPORT_STREAM_FATAL
21
+#define REPORT_STREAM_FATAL stderr
22
+#endif
24 23
 
25
-#ifndef REPORT_FORMAT_PREFIX_INFO
26
-#define REPORT_FORMAT_PREFIX_INFO  "Info: "
24
+#ifndef REPORT_PREFIX_INFO
25
+#define REPORT_PREFIX_INFO  "Info: "
27 26
 #endif
28
-#ifndef REPORT_FORMAT_PREFIX_ERROR
29
-#define REPORT_FORMAT_PREFIX_ERROR "Error: "
27
+#ifndef REPORT_PREFIX_ERROR
28
+#define REPORT_PREFIX_ERROR "Error: "
30 29
 #endif
31
-#ifndef REPORT_FORMAT_PREFIX_FATAL
32
-#define REPORT_FORMAT_PREFIX_FATAL "Fatal: "
30
+#ifndef REPORT_PREFIX_FATAL
31
+#define REPORT_PREFIX_FATAL "Fatal: "
33 32
 #endif
34 33
 
34
+#define report_info( ...) report(REPORT_STREAM_INFO,  REPORT_PREFIX_INFO,  0, __VA_ARGS__)
35
+#define report_error(...) report(REPORT_STREAM_ERROR, REPORT_PREFIX_ERROR, 0, __VA_ARGS__)
36
+#define report_fatal(...) report(REPORT_STREAM_FATAL, REPORT_PREFIX_FATAL, 1, __VA_ARGS__)
37
+
35 38
 
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
-    );
39
+static int report(
40
+    FILE * stream,
41
+    char const * prefix,
42
+    int fatal,
43
+    int report_errno,
44
+    char const * format,
45
+    ...
46
+) {
47
+    fprintf(stream, "%s", prefix);
46 48
     {
47 49
         va_list ap;
48 50
         va_start(ap, format);
... ...
@@ -52,7 +54,7 @@ static int report(int report, int report_errno, char const * format, ...) {
52 54
     if (report_errno)
53 55
         fprintf(stream, ": %s", strerror(report_errno));
54 56
     fprintf(stream, "\n");
55
-    if (report >= REPORT_FATAL)
57
+    if (fatal)
56 58
         exit(EXIT_FAILURE);
57
-    return report;
59
+    return EXIT_FAILURE;
58 60
 }
... ...
@@ -1,7 +1,24 @@
1
-// TODO: Add `listen` and `accept` for if the socket has a `STREAM` `socktype`.
1
+// TODO: Break out `run_*` into `*.{c,h}`?
2
+// TODO: Harmonize all reporting strings and comments.
3
+// TODO: Harmonize all conditional and loop statements.
4
+// TODO: Add `listen` and `accept` if the socket has a `STREAM` `socktype`.
2 5
 // TODO: Compare to the example in `getaddrinfo(3)`.
3 6
 // TODO: Mention `getaddrinfo` and the RFC 3484 sorting order in readme.
4 7
 // TODO: Change client store from queue to binary tree?
8
+// TODO: Create graph of parameter flow.
9
+// TODO: Use Kaj and Börje in readme example.
10
+// TODO: Document idioms:
11
+// - Trim: `buflen -= buflen && buf[buflen - 1] == char;`
12
+// - Format: `buflen += snprintf(&buf[buflen], BUFLEN_MAX - buflen, format, args...);`
13
+// TODO: Document "size" for raw bytes and "len" for "logical" data (e.g.
14
+// excluding string terminating null).
15
+// TODO: Give each client an `id`.
16
+// TODO: Also make the server send a timestamp base on join?
17
+// TODO: `#include <stdbool.h>` and change any relevant `int`s to `bool`s.
18
+// TODO: Change to `run_*(int * sockfd, int sockfdlen, bool nonblock)`.
19
+// TODO: Do different things on `initsockfd` depending on `family`.
20
+// TODO: We can combine IDs and actions: an ID of 0 implies a JOIN action,
21
+// otherwise a MESSAGE action.
5 22
 
6 23
 /// Headers
7 24
 
... ...
@@ -25,17 +42,15 @@
25 42
 #include <termios.h>
26 43
 
27 44
 // Other
28
-#include "args.h"
45
+#include "arg.h"
29 46
 #include "report.h"
30 47
 
31 48
 
32
-/// Parameters
49
+/// Constants
50
+
51
+//// Program
33 52
 
34
-//// Application
35 53
 #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 54
 
40 55
 //// Arguments
41 56
 // TODO
... ...
@@ -49,12 +64,14 @@
49 64
 
50 65
 //// Command line interface
51 66
 #define VERSION \
52
-    PROGNAME " " VERSION_STR " - " DESCRIPTION "\n"
67
+    PROGNAME " 1.0\n"
53 68
 #define USAGE \
54 69
     "Usage:\n" \
55 70
     "  " PROGNAME " (server|client) [options]\n" \
56 71
     "  " PROGNAME " -h|--help\n" \
57 72
     "  " PROGNAME " --version\n"
73
+#define DESCRIPTION \
74
+    "Chat with unlimited number of peers through a variety of sockets.\n"
58 75
 #define OPTIONS \
59 76
     "Options:\n" \
60 77
     "  -f <family>    [default: " DEFAULT_FAMILY   "]\n" \
... ...
@@ -64,15 +81,15 @@
64 81
     "  -s <service>   [default: " DEFAULT_SERVICE  "]\n"
65 82
 
66 83
 //// Implementation
67
-#define BUF_SIZE _POSIX_MAX_CANON
84
+#define BUFLEN_MAX _POSIX_MAX_CANON
68 85
 
69 86
 
70 87
 /// Forward declarations
71 88
 
72 89
 //// Implementation
73
-int getsockfd(
74
-    char const * action_name,
75
-    int (*action)(int sockfd, struct sockaddr const * addr, socklen_t addrlen),
90
+int initsockfd(
91
+    char const * init_name,
92
+    int (*init)(int sockfd, struct sockaddr const * addr, socklen_t addrlen),
76 93
     int flags,
77 94
     int family,
78 95
     int socktype,
... ...
@@ -87,105 +104,103 @@ void run_client(int sockfd);
87 104
 /// Data
88 105
 
89 106
 //// Arguments
107
+#define ROLE(ROLE, INIT, FLAGS) { #ROLE, #INIT, INIT, FLAGS, run_##ROLE }
90 108
 struct role {
91 109
     char const * name;
92
-    char const * action_name;
93
-    int (*action)(int sockfd, struct sockaddr const * addr, socklen_t addrlen);
110
+    char const * init_name;
111
+    int (*init)(int sockfd, struct sockaddr const * addr, socklen_t addrlen);
94 112
     int flags;
95 113
     void (*run)(int sockfd);
96 114
 };
97
-struct option {
98
-    char const * name;
99
-    int value;
100
-};
101
-#define ROLE(NAME, ACTION, FLAGS) { #NAME, #ACTION, ACTION, FLAGS, run_##NAME }
102 115
 static struct role roles[] = {
103 116
     ROLE(server, bind, AI_PASSIVE),
104 117
     ROLE(client, connect, 0),
105 118
 };
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),
119
+
120
+#define CONST_FAMILY(FAMILY) { #FAMILY, AF_##FAMILY }
121
+static struct arg_const families[] = {
122
+    CONST_FAMILY(UNSPEC),
123
+    CONST_FAMILY(INET),
124
+    CONST_FAMILY(INET6),
125
+    CONST_FAMILY(UNIX),
114 126
 };
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),
127
+
128
+#define CONST_SOCKTYPE(SOCKTYPE) { #SOCKTYPE, SOCK_##SOCKTYPE }
129
+static struct arg_const socktypes[] = {
130
+    // { "0", 0 },
131
+    CONST_SOCKTYPE(DGRAM),
132
+    // CONST_SOCKTYPE(SEQPACKET),
133
+    // CONST_SOCKTYPE(STREAM),
134
+    // CONST_SOCKTYPE(RAW),
124 135
 };
125
-#define OPTION_PROTOCOL(PROTOCOL) { #PROTOCOL, IPPROTO_##PROTOCOL }
126
-static struct option protocols[] = {
136
+
137
+#define CONST_PROTOCOL(PROTOCOL) { #PROTOCOL, IPPROTO_##PROTOCOL }
138
+static struct arg_const protocols[] = {
127 139
     { "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),
140
+    // CONST_PROTOCOL(IP),
141
+    // CONST_PROTOCOL(IPV6),
142
+    // CONST_PROTOCOL(ICMP),
143
+    // CONST_PROTOCOL(TCP),
144
+    // CONST_PROTOCOL(UDP),
145
+    // CONST_PROTOCOL(RAW),
134 146
 };
135 147
 
136 148
 
137 149
 /// 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);
150
+void version    (FILE * stream) { fprintf(stream, "%s", VERSION);     }
151
+void usage      (FILE * stream) { fprintf(stream, "%s", USAGE);       }
152
+void description(FILE * stream) { fprintf(stream, "%s", DESCRIPTION); }
153
+void options    (FILE * stream) { fprintf(stream, "%s", OPTIONS);     }
154
+void args       (FILE * stream) {
155
+    ARG_PRINT(stream, family,   families ); fprintf(stream, "\n");
156
+    ARG_PRINT(stream, socktype, socktypes); fprintf(stream, "\n");
157
+    ARG_PRINT(stream, protocol, protocols);
145 158
 }
146 159
 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);
160
+    version    (stream); fprintf(stream, "\n");
161
+    usage      (stream); fprintf(stream, "\n");
162
+    description(stream); fprintf(stream, "\n");
163
+    options    (stream); fprintf(stream, "\n");
164
+    args       (stream);
151 165
 }
152 166
 
153 167
 
154
-/// main
168
+/// Main
169
+
155 170
 int main(int argc, char * argv[]) {
156 171
     // 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()
172
+    ARG_SPECIALS()
173
+    ARG_DECLARE(role,     NULL)
174
+    ARG_DECLARE(family,   DEFAULT_FAMILY)
175
+    ARG_DECLARE(socktype, DEFAULT_SOCKTYPE)
176
+    ARG_DECLARE(protocol, DEFAULT_PROTOCOL)
177
+    ARG_DECLARE(node,     DEFAULT_NODE)
178
+    ARG_DECLARE(service,  DEFAULT_SERVICE)
179
+    ARG_POSITIONAL(role)
180
+    ARG_GETOPT_BEGIN("f:t:p:n:s:")
181
+    ARG_GETOPT_WITH_ARGUMENT('f', family)
182
+    ARG_GETOPT_WITH_ARGUMENT('t', socktype)
183
+    ARG_GETOPT_WITH_ARGUMENT('p', protocol)
184
+    ARG_GETOPT_WITH_ARGUMENT('n', node)
185
+    ARG_GETOPT_WITH_ARGUMENT('s', service)
186
+    ARG_GETOPT_END()
187
+    ARG_CONVERT_FIND(role, role, roles)
188
+    ARG_CONVERT_FIND_CONST(family,   families)
189
+    ARG_CONVERT_FIND_CONST(socktype, socktypes)
190
+    ARG_CONVERT_FIND_CONST(protocol, protocols)
191
+    ARG_CONVERT_NULL(node)
192
+    ARG_CONVERT_NULL(service)
193
+    ARG_CATCH()
179 194
 
180 195
     // Implementation
181
-    report_info(0, "Using buffer size %d", BUF_SIZE);
182
-    role->run(getsockfd(
183
-        role->action_name,
184
-        role->action,
196
+    report_info(0, "Using buffer length %d", BUFLEN_MAX);
197
+    role->run(initsockfd(
198
+        role->init_name,
199
+        role->init,
185 200
         role->flags,
186
-        family->value,
187
-        socktype->value,
188
-        protocol->value,
201
+        family,
202
+        socktype,
203
+        protocol,
189 204
         node,
190 205
         service
191 206
     ));
... ...
@@ -194,10 +209,33 @@ int main(int argc, char * argv[]) {
194 209
 
195 210
 /// Implementation
196 211
 
197
-//// getsockfd
198
-int getsockfd(
199
-    char const * action_name,
200
-    int (*action)(int sockfd, struct sockaddr const * addr, socklen_t addrlen),
212
+//// Common
213
+
214
+// `NI_MAXHOST` and `NI_MAXSERV` are non-standard.
215
+static char gai_host[1025];
216
+static char gai_serv[32];
217
+static int gai_errno;
218
+
219
+#define ACTION_NONE    0
220
+#define ACTION_LEAVE   1
221
+#define ACTION_JOIN    2
222
+#define ACTION_MESSAGE 3
223
+struct action {
224
+    char const * name;
225
+    int value;
226
+};
227
+#define ACTION(ACTION) { #ACTION, ACTION_##ACTION }
228
+static struct action actions[] = {
229
+    ACTION(NONE),
230
+    ACTION(LEAVE),
231
+    ACTION(JOIN),
232
+    ACTION(MESSAGE),
233
+};
234
+
235
+// TODO: Call and print results of `getsockname` and `getpeername`?
236
+int initsockfd(
237
+    char const * init_name,
238
+    int (*init)(int sockfd, struct sockaddr const * addr, socklen_t addrlen),
201 239
     int flags,
202 240
     int family,
203 241
     int socktype,
... ...
@@ -205,63 +243,87 @@ int getsockfd(
205 243
     char const * node,
206 244
     char const * service
207 245
 ) {
208
-    int gai_errno;
209 246
     int sockfd;
247
+    // Initialize socket.
210 248
     {
211 249
         struct addrinfo * addrinfos;
212 250
         // Get addresses.
213 251
         {
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.
252
+            // The assumption of these flags are non-standard.
253
+            flags |= AI_V4MAPPED | AI_ADDRCONFIG;
254
+            struct addrinfo hints = {
255
+                flags, family, socktype, protocol, 0, 0, 0, 0
256
+            };
224 257
             if (0 != (gai_errno = getaddrinfo(
225 258
                 node, service, &hints, &addrinfos
226 259
             )))
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)
260
+                report_fatal(
261
+                    gai_errno == EAI_SYSTEM ? errno : 0,
262
+                    "Failed to get %s addresses for '%s:%s': %s",
263
+                    init_name,
264
+                    node    ? node    : "",
265
+                    service ? service : "",
266
+                    gai_strerror(gai_errno)
230 267
                 );
231 268
         }
232
-        // Try action on addresses until one works.
233
-        report_info(0, "Trying to %s...", action_name);
269
+        // Try init on addresses until one works.
234 270
         struct addrinfo * addrinfo;
235 271
         for (
236 272
             addrinfo = addrinfos;
237 273
             addrinfo != NULL;
238 274
             addrinfo = addrinfo->ai_next
239 275
         ) {
276
+            // Get address name.
277
+            if (0 != (gai_errno = getnameinfo(
278
+                addrinfo->ai_addr,
279
+                addrinfo->ai_addrlen,
280
+                gai_host,
281
+                sizeof(gai_host),
282
+                gai_serv,
283
+                sizeof(gai_serv),
284
+                NI_NUMERICHOST | NI_NUMERICSERV
285
+            ))) {
286
+                report_error(
287
+                    gai_errno == EAI_SYSTEM ? errno : 0,
288
+                    "Failed to get %s address name: %s",
289
+                    init_name,
290
+                    gai_strerror(gai_errno)
291
+                );
292
+                continue;
293
+            }
294
+            report_info(0, "Trying to %s to '%s:%s'...",
295
+                init_name, gai_host, gai_serv
296
+            );
240 297
             // Create socket.
241 298
             if (-1 == (sockfd = socket(
242 299
                 addrinfo->ai_family,
243 300
                 addrinfo->ai_socktype,
244 301
                 addrinfo->ai_protocol
245 302
             ))) {
246
-                report_info(errno, "> Failed to create socket");
303
+                report_error(errno, "Failed to create %s socket", init_name);
247 304
                 continue;
248 305
             }
249
-            // Perform action.
250
-            if (-1 == action(
306
+            // Perform init.
307
+            if (-1 == init(
251 308
                 sockfd,
252 309
                 addrinfo->ai_addr,
253 310
                 addrinfo->ai_addrlen
254 311
             )) {
312
+                report_error(errno, "Failed to %s to '%s:%s'",
313
+                    init_name, gai_host, gai_serv
314
+                );
255 315
                 close(sockfd);
256
-                report_info(errno, "> Failed to %s", action_name);
257 316
                 continue;
258 317
             }
259 318
             // Succeeded.
260
-            report_info(0, "Succeeded to %s", action_name);
319
+            report_info(0, "Succeeded to %s to '%s:%s'",
320
+                init_name, gai_host, gai_serv
321
+            );
261 322
             break;
262 323
         }
324
+        // Fail.
263 325
         if (NULL == addrinfo)
264
-            report_fatal(0, "Failed to get socket");
326
+            report_fatal(0, "Failed to %s", init_name);
265 327
         // Clean up.
266 328
         freeaddrinfo(addrinfos);
267 329
     }
... ...
@@ -269,13 +331,13 @@ int getsockfd(
269 331
 }
270 332
 
271 333
 
272
-//// run_server
273
-void run_server(int sockfd) {
274
-    int gai_errno;
334
+//// Server
275 335
 
336
+// TODO: Also store `addlen` in `client` (and use in `memcmp`)?
337
+void run_server(int sockfd) {
276 338
     // Client data.
277 339
     struct client_data {
278
-        char * name;
340
+        char * user_name;
279 341
     };
280 342
 
281 343
     // Client accounting.
... ...
@@ -283,198 +345,242 @@ void run_server(int sockfd) {
283 345
         struct client * next;
284 346
         struct client * prev;
285 347
         struct sockaddr_storage addr;
286
-        struct client_data data;
348
+        void * data;
287 349
     };
288 350
     struct client * clients = NULL;
289 351
 
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);
352
+    // Allocate socket buffer.
353
+    ssize_t sockbuflen;
354
+    char * sockbuf = malloc(BUFLEN_MAX + 1);
355
+    if (!sockbuf)
356
+        report_fatal(errno, "Failed to allocate %s of size %d",
357
+            "input buffer", BUFLEN_MAX + 1
358
+        );
295 359
 
296 360
     // Talk to clients.
297 361
     while (1) {
298
-        // Peek receive length and address.
299
-        int recvlen;
362
+        // Get client and action.
363
+        // `NI_MAXHOST` and `NI_MAXSERV` are non-standard.
300 364
         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 365
         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(
366
+        struct action action;
367
+        {
368
+            // Peek receive.
369
+            ssize_t recvlen;
370
+            socklen_t addrlen = sizeof(addr);
371
+            if (-1 == (recvlen = recvfrom(
372
+                sockfd,
373
+                // TODO
374
+                sockbuf,
375
+                BUFLEN_MAX,
376
+                // NULL,
377
+                // 0,
378
+                MSG_PEEK, // TODO: Also specify `MSG_TRUNC` so that we don't have to give the buffer?
355 379
                 (struct sockaddr *)&addr,
356
-                addrlen,
357
-                &buf[buflen],
358
-                BUF_SIZE - buflen - 1,
359
-                NULL,
360
-                0,
361
-                0
380
+                &addrlen
362 381
             )))
363
-                report_error(gai_errno == EAI_SYSTEM ? errno : 0,
364
-                    "Failed to get client address node name: %s",
365
-                    gai_strerror(gai_errno)
382
+                report_fatal(errno, "Failed to peek receive");
383
+            // Check for too large addresses (guaranteed not to happen).
384
+            if (addrlen > sizeof(addr))
385
+                report_fatal(0,
386
+                    "Failed to store address of size %d, max size is %d",
387
+                    addrlen, sizeof(addr)
366 388
                 );
367
-            buflen += strlen(&buf[buflen]);
368
-            // Add separator.
369
-            buflen += snprintf(&buf[buflen], BUF_SIZE - buflen - 1, ":");
370
-            // Get client address service name.
389
+            // Get client address name.
371 390
             if (0 != (gai_errno = getnameinfo(
372 391
                 (struct sockaddr *)&addr,
373 392
                 addrlen,
374
-                NULL,
375
-                0,
376
-                &buf[buflen],
377
-                BUF_SIZE - buflen - 1,
378
-                0
393
+                gai_host,
394
+                sizeof(gai_host),
395
+                gai_serv,
396
+                sizeof(gai_serv),
397
+                NI_NUMERICHOST | NI_NUMERICSERV
379 398
             )))
380
-                report_error(gai_errno == EAI_SYSTEM ? errno : 0,
381
-                    "Failed to get client address service name: %s",
399
+                report_fatal(
400
+                    gai_errno == EAI_SYSTEM ? errno : 0,
401
+                    "Failed to get client address name: %s",
382 402
                     gai_strerror(gai_errno)
383 403
                 );
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);
404
+            // Look up client.
405
+            for (
406
+                client = clients;
407
+                client != NULL;
408
+                client = client->next
409
+            )
410
+                if (0 == memcmp(&client->addr, &addr, sizeof(addr)))
411
+                    break;
412
+            // Look up action.
413
+            action = actions[!!recvlen<<1 | !!client<<0];
414
+            // Report.
415
+            report_info(0, "Received %d byte%s from '%s:%s' %s",
416
+                recvlen,
417
+                recvlen == 1 ? "" : "s",
418
+                gai_host,
419
+                gai_serv,
420
+                action.name
421
+            );
422
+        }
423
+        // No action.
424
+        if (action.value == ACTION_NONE) {
425
+            // Receive none.
426
+            if (-1 == recv(sockfd, NULL, 0, 0))
427
+                report_fatal(errno, "Failed to receive %s", action.name);
428
+            continue;
429
+        }
430
+        // Client left.
431
+        else if (action.value == ACTION_LEAVE) {
432
+            // Receive leave.
433
+            if (-1 == recv(sockfd, NULL, 0, 0))
434
+                report_fatal(errno, "Failed to receive %s", action.name);
435
+            // Client data.
436
+            struct client_data * client_data = client->data;
437
+            free(client_data->user_name);
438
+            free(client_data);
439
+            // Client accounting.
440
+            remque(client);
441
+            free(client);
442
+            // Done.
443
+            continue;
444
+        }
445
+        // Client joined.
446
+        else if (action.value == ACTION_JOIN) {
447
+            sockbuflen = 0;
448
+            // Receive.
449
+            {
450
+                ssize_t recvlen;
451
+                if (-1 == (recvlen = recv(
452
+                    sockfd, sockbuf, BUFLEN_MAX - sockbuflen, 0
453
+                )))
454
+                    report_fatal(errno, "Failed to receive %s", action.name);
455
+                sockbuflen += recvlen;
456
+            }
457
+            // Validate.
458
+            if (!sockbuflen || sockbuf[sockbuflen - 1] != '\0') {
459
+                report_error(0, "Malformed %s", action.name);
460
+            }
461
+            // Client data.
462
+            struct client_data * client_data;
463
+            if (NULL == (client_data = malloc(sizeof(*client_data))))
464
+                report_fatal(errno, "Failed to allocate %s of size %d",
465
+                    "client data", sizeof(*client_data)
466
+                );
467
+            if (NULL == (client_data->user_name = strdup(sockbuf)))
468
+                report_fatal(errno, "Failed to allocate %s",
469
+                    "client user name"
470
+                );
471
+            // Client accounting.
472
+            if (NULL == (client = calloc(1, sizeof(*client))))
473
+                report_fatal(errno, "Failed allocate %s of size %d",
474
+                    "client", sizeof(*client)
475
+                );
476
+            insque(client, &clients);
477
+            client->addr = addr;
478
+            client->data = client_data;
479
+            // Done.
398 480
             continue;
399 481
         }
400 482
         // 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");
483
+        else if (action.value == ACTION_MESSAGE) {
484
+            sockbuflen = 0;
485
+            // Client data.
486
+            struct client_data * client_data = client->data;
487
+            // TODO: `snprintf` returns how many bytes *would* have been
488
+            // written! Use `strlen`?
489
+            sockbuflen += snprintf(
490
+                &sockbuf[sockbuflen], BUFLEN_MAX - sockbuflen, "[%s:%s] %s: ",
491
+                gai_host, gai_serv, client_data->user_name
492
+            );
493
+            // Receive message.
494
+            {
495
+                ssize_t recvlen;
496
+                if (-1 == (recvlen = recv(
497
+                    sockfd, &sockbuf[sockbuflen], BUFLEN_MAX - sockbuflen, 0
498
+                )))
499
+                    report_fatal(errno, "Failed to receive %s", action.name);
500
+                sockbuflen += recvlen;
501
+            }
502
+            sockbuflen -= sockbuflen && sockbuf[sockbuflen - 1] == '\0';
503
+            // Finalize buffer.
504
+            sockbuf[sockbuflen++] = '\0';
505
+            // Send message.
506
+            for (
507
+                client = clients;
508
+                client != NULL;
509
+                client = client->next
510
+            ) {
511
+                if (-1 == sendto(
512
+                    sockfd,
513
+                    sockbuf,
514
+                    sockbuflen,
515
+                    0,
516
+                    (struct sockaddr *)&client->addr,
517
+                    sizeof(client->addr)
518
+                ))
519
+                    report_fatal(errno, "Failed to send %s", action.name);
520
+            }
521
+        } else {
522
+            report_fatal(0, "Unexpected action %d", action.value);
432 523
         }
433 524
     }
434 525
 }
435 526
 
436 527
 
437
-//// run_client
528
+//// Client
529
+
530
+static struct termios termios_stdin;
531
+static void termios_restore() {
532
+    if (-1 == tcsetattr(STDIN_FILENO, TCSANOW, &termios_stdin))
533
+        report_fatal(errno, "Failed to restore terminal settings");
534
+}
535
+static void termios_setup() {
536
+    if (-1 == tcgetattr(STDIN_FILENO, &termios_stdin))
537
+        report_fatal(errno, "Failed to get terminal settings");
538
+    {
539
+        struct termios termios = termios_stdin;
540
+        termios.c_lflag &= ~(ICANON | ECHO);
541
+        if (-1 == tcsetattr(STDIN_FILENO, TCSANOW, &termios))
542
+            report_fatal(errno, "Failed to set terminal settings");
543
+    }
544
+    atexit(termios_restore);
545
+}
546
+
438 547
 void run_client(int sockfd) {
439 548
     // Allocate socket buffer.
440
-    int sockbuflen;
441
-    char * sockbuf = malloc(BUF_SIZE);
549
+    ssize_t sockbuflen;
550
+    char * sockbuf = malloc(BUFLEN_MAX + 1);
442 551
     if (!sockbuf)
443
-        report_fatal(errno, "Failed to allocate socket buffer of size %d",
444
-            BUF_SIZE
552
+        report_fatal(errno, "Failed to allocate %s of size %d",
553
+            "input buffer", BUFLEN_MAX + 1
445 554
         );
446 555
 
447 556
     // Allocate input buffer.
448
-    int inputbuflen;
449
-    char * inputbuf = malloc(BUF_SIZE);
557
+    ssize_t inputbuflen;
558
+    char * inputbuf = malloc(BUFLEN_MAX + 1);
450 559
     if (!inputbuf)
451
-        report_fatal(errno, "Failed to allocate input buffer of size %d",
452
-            BUF_SIZE
560
+        report_fatal(errno, "Failed to allocate %s of size %d",
561
+            "socket buffer", BUFLEN_MAX + 1
453 562
         );
454 563
 
455 564
     // 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 565
     {
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");
566
+        struct action action = actions[ACTION_JOIN];
567
+        // Get user name.
568
+        printf("User name: ");
569
+        fflush(stdout);
570
+        if (-1 == (inputbuflen = read(STDIN_FILENO, inputbuf, BUFLEN_MAX)))
571
+            report_fatal(errno, "Failed to read user name");
572
+        inputbuflen -= inputbuflen && inputbuf[inputbuflen - 1] == '\n';
573
+        // Send join.
574
+        inputbuf[inputbuflen++] = '\0';
575
+        if (-1 == send(sockfd, inputbuf, inputbuflen, 0))
576
+            report_fatal(errno, "Failed to send %s", action.name);
577
+        if (-1 == recv(sockfd, sockbuf, BUFLEN_MAX, MSG_PEEK))
578
+            report_fatal(errno, "Failed to receive %s", action.name);
476 579
     }
477 580
 
581
+    // Setup terminal.
582
+    termios_setup();
583
+
478 584
     // Talk to user and server.
479 585
     inputbuflen = 0;
480 586
     while (1) {
... ...
@@ -488,11 +594,11 @@ void run_client(int sockfd) {
488 594
             report_fatal(errno, "Failed to wait for data");
489 595
         }
490 596
         // Clear input line.
491
-        printf("\r%*s\r", inputbuflen, "");
597
+        printf("\r%*s\r", (int)inputbuflen, "");
492 598
         // Socket data available.
493 599
         if (FD_ISSET(sockfd, &rfds)) {
494 600
             // Receive.
495
-            if (-1 == (sockbuflen = recv(sockfd, sockbuf, BUF_SIZE - 1, 0)))
601
+            if (-1 == (sockbuflen = recv(sockfd, sockbuf, BUFLEN_MAX, 0)))
496 602
                 report_fatal(errno, "Failed to receive");
497 603
             sockbuf[sockbuflen++] = '\0';
498 604
             // Print socket line.
... ...
@@ -503,9 +609,9 @@ void run_client(int sockfd) {
503 609
             // Read.
504 610
             char c;
505 611
             if (-1 == read(STDIN_FILENO, &c, 1))
506
-                report_fatal(errno, "Failed to read input");
612
+                report_fatal(errno, "Failed to read");
507 613
             // Printable with non-full buffer.
508
-            if (isprint(c) && inputbuflen < BUF_SIZE - 1) {
614
+            if (isprint(c) && inputbuflen < BUFLEN_MAX) {
509 615
                 // Add.
510 616
                 inputbuf[inputbuflen++] = c;
511 617
             // Backspace.
... ...
@@ -516,8 +622,7 @@ void run_client(int sockfd) {
516 622
             // Enter.
517 623
             } else if (c == '\n' || c == '\r') {
518 624
                 // Send.
519
-                if (inputbuflen)
520
-                    inputbuf[inputbuflen++] = '\0';
625
+                inputbuf[inputbuflen] = '\0';
521 626
                 if (-1 == send(sockfd, inputbuf, inputbuflen, 0))
522 627
                     report_fatal(errno, "Failed to send");
523 628
                 // Quit if input line empty.
... ...
@@ -528,11 +633,8 @@ void run_client(int sockfd) {
528 633
             }
529 634
         }
530 635
         // Print input line.
531
-        printf("%.*s", inputbuflen, inputbuf);
636
+        // TODO: Have we not "finalized" the buffer here?
637
+        printf("%.*s", (int)inputbuflen, inputbuf);
532 638
         fflush(stdout);
533 639
     }
534
-
535
-    // Restore terminal.
536
-    if (-1 == tcsetattr(STDIN_FILENO, TCSANOW, &termios_stdin))
537
-        report_fatal(errno, "Failed to restore terminal settings");
538 640
 }