~aritra1911/mc_server

237d47470fb242737fa29445d738f23574a8276a — Aritra Sarkar 1 year, 11 months ago 0ccfbc0
Conform more closely to FreeBSD STYLE(9)
8 files changed, 268 insertions(+), 168 deletions(-)

M events.c
M helpers.c
M helpers.h
M main.c
M response.c
M session.c
M session.h
M tpool.c
M events.c => events.c +60 -53
@@ 1,4 1,4 @@
/*
/*-
 * events.c  Mini Chat Server Events Handler
 * Copyright (C) 2022  Aritra Sarkar
 *


@@ 36,21 36,21 @@
#include "response.h"

typedef struct writers {
    size_t count;
    size_t          count;
    pthread_mutex_t mutex;
    pthread_cond_t done;
    pthread_cond_t  done;
} writers_t;

typedef struct accept_params {
    session_t *sessionp;
    writers_t *writersp;
    session_t   *sessionp;
    writers_t   *writersp;
} accept_params_t;

/* Receive And Send (RAS), not to be confused by RAS Syndrome */
typedef struct ras_params {
    int fd;
    session_t *sessionp;
    writers_t *writersp;
    int         fd;
    session_t   *sessionp;
    writers_t   *writersp;
} ras_params_t;

static int writers_init(writers_t *);


@@ 63,7 63,7 @@ static void recv_and_send(int tid, void *arg);
static int
writers_init(writers_t *writersp)
{
    char err_str[STRERROR_BUFLEN];
    char    err_str[STRERROR_BUFLEN];

    writersp->count = 0;



@@ 87,7 87,7 @@ writers_init(writers_t *writersp)
static int
writers_increment(writers_t *writersp)
{
    char err_str[STRERROR_BUFLEN];
    char    err_str[STRERROR_BUFLEN];

    if ( pthread_mutex_lock(&writersp->mutex) ) {
        get_err_str(errno, err_str);


@@ 111,8 111,8 @@ writers_increment(writers_t *writersp)
static int
writers_decrement(writers_t *writersp)
{
    char err_str[STRERROR_BUFLEN];
    int retval = 0;
    char    err_str[STRERROR_BUFLEN];
    int     retval = 0;

    if ( pthread_mutex_lock(&writersp->mutex) ) {
        get_err_str(errno, err_str);


@@ 136,14 136,15 @@ writers_decrement(writers_t *writersp)
                __FILE__, __LINE__ - 3, err_str);
        return -1;
    }

    return retval;
}

static int
writers_wait(writers_t *writersp)
{
    char err_str[STRERROR_BUFLEN];
    int retval = 0;
    char    err_str[STRERROR_BUFLEN];
    int     retval = 0;

    if ( pthread_mutex_lock(&writersp->mutex) ) {
        get_err_str(errno, err_str);


@@ 175,7 176,7 @@ writers_wait(writers_t *writersp)
static int
writers_destroy(writers_t *writersp)
{
    char err_str[STRERROR_BUFLEN];
    char    err_str[STRERROR_BUFLEN];

    writersp->count = 0xDEAD4BAD;   /* prime */



@@ 199,12 200,12 @@ writers_destroy(writers_t *writersp)
int
process_events(int num_events, tpool_t *tpoolp, session_t *sessionp)
{
    size_t i;
    int *fds, *fdp, listener, retval;
    char err_str[STRERROR_BUFLEN];
    size_t          i;
    int             *fds, *fdp, listener, retval;
    char            err_str[STRERROR_BUFLEN];
    accept_params_t *accept_paramsp;
    ras_params_t *ras_paramsp;
    writers_t writers;
    ras_params_t    *ras_paramsp;
    writers_t       writers;

    listener = sessionp->pfds[0].fd;
    retval = 0;


@@ 223,10 224,12 @@ process_events(int num_events, tpool_t *tpoolp, session_t *sessionp)
        return -1;
    }

    /* Collect the `fds` that we need to `recv()` or `accept()` on so
    /*
     * Collect the `fds` that we need to `recv()` or `accept()` on so
     * that we can give a window of time where we do not impose a read
     * lock on `session` and let the worker threads mutating `session`
     * a chance to finish. */
     * a chance to finish.
     */
    for (i = 0, fdp = fds; num_events && i < sessionp->fd_count; i++) {
        if ( sessionp->pfds[i].revents & POLLIN ) {
            *fdp++ = sessionp->pfds[i].fd;


@@ 242,14 245,15 @@ process_events(int num_events, tpool_t *tpoolp, session_t *sessionp)
        goto free_fds;
    }

    /* for each `fd` in `fds`, we need to do a `recv()` unless it's the
    /*
     * for each `fd` in `fds`, we need to do a `recv()` unless it's the
     * `listener` for which we `accept()` a new client. If the thread
     * pool's work queue is full, `tpool_add_work()` shall block while
     * adding new work. Since we do not impose a read lock on `session`
     * at this window of time, even if every queued work require a
     * write lock on `session`, they can proceed to drain the queue and
     * allow new work to be added. */

     * allow new work to be added.
     */
    for (fdp = fds; *fdp; fdp++) {
        if ( *fdp == listener ) {
            if ( !(accept_paramsp = malloc(sizeof(accept_params_t))) ) {


@@ 292,15 296,16 @@ process_events(int num_events, tpool_t *tpoolp, session_t *sessionp)
            ras_paramsp->sessionp = sessionp;
            ras_paramsp->writersp = &writers;

            /* This potentially holds a write lock on `session` in
            /*
             * This potentially holds a write lock on `session` in
             * case session data needs to be modified like for eg. in
             * case of a nick change or client disconnect. Once it
             * figures out that `session` doesn't need any modification
             * or such a modification has already been performed, it
             * releases the write lock and switches to a read lock for
             * distribution or announcement of message to all the other
             * clients. */

             * clients.
             */
            if ( tpool_add_work(tpoolp, recv_and_send, ras_paramsp) == -1 ) {
                fprintf(stderr, " ERR : %s:%i => `tpool_add_work()`:"
                                      " Failed to add work to queue!\n",


@@ 319,9 324,10 @@ process_events(int num_events, tpool_t *tpoolp, session_t *sessionp)
        }
    }

    /* Wait for all writer threads to release the lock before
     * read-locking `session` for `poll()`ing again. */

    /*
     * Wait for all writer threads to release the lock before
     * read-locking `session` for `poll()`ing again.
     */
    if ( writers_wait(&writers) == -1 ) {
        fprintf(stderr, " ERR : %s:%i => `writers_wait()`:"
                              " Failed to wait for writers to return!\n",


@@ 352,16 358,18 @@ free_fds:
static void
accept_client(int thread_id, void *arg)
{
    int listener, newfd, ret = 0;
    char err_str[STRERROR_BUFLEN];
    int                     listener, newfd, ret=0;
    char                    err_str[STRERROR_BUFLEN];
    struct sockaddr_storage remoteaddr;
    socklen_t addrlen;
    session_t *sessionp;
    writers_t *writersp;
    socklen_t               addrlen;
    session_t               *sessionp;
    writers_t               *writersp;

    /* INET6_ADDRSTRLEN > INET_ADDRSTRLEN and hence big enough to fit
     * an IPv4 address as well. */
    char remoteIP[INET6_ADDRSTRLEN] = "";
    /*
     * INET6_ADDRSTRLEN > INET_ADDRSTRLEN and hence big enough to fit
     * an IPv4 address as well.
     */
    char                    remoteIP[INET6_ADDRSTRLEN]="";

    sessionp = ((accept_params_t *) arg)->sessionp;
    writersp = ((accept_params_t *) arg)->writersp;


@@ 429,22 437,21 @@ accept_client(int thread_id, void *arg)
static void
recv_and_send(int thread_id, void *arg)
{
    int fd, announce, ret = 0;
    ssize_t num_bytes;
    size_t i;
    char buf[BUFLEN] = "", err_str[STRERROR_BUFLEN];
    session_t *sessionp;
    writers_t *writersp;
    ResponseError err_code;

    fd       = ((ras_params_t *) arg)->fd;
    sessionp = ((ras_params_t *) arg)->sessionp;
    writersp = ((ras_params_t *) arg)->writersp;
    int             fd, announce, ret = 0;
    ssize_t         num_bytes;
    size_t          i;
    char            buf[BUFLEN]="", err_str[STRERROR_BUFLEN];
    session_t       *sessionp;
    writers_t       *writersp;
    ResponseError   err_code;

    fd          =   ((ras_params_t *) arg)->fd;
    sessionp    =   ((ras_params_t *) arg)->sessionp;
    writersp    =   ((ras_params_t *) arg)->writersp;
    announce    =   0;

    free(arg);

    announce = 0;

    if ( (num_bytes = recv(fd, buf, BUFLEN, 0)) == -1 ) {
        get_err_str(errno, err_str);
        fprintf(stderr, " ERR : [THREAD #%02i] %s:%i => `recv()`: %s\n",


@@ 531,7 538,7 @@ disconnect:
            }
        } else {
            pthread_rwlock_rdlock(&sessionp->lock);
            for (i = 1 /* avoid listener */; i < sessionp->fd_count; i++) {
            for (i = 1  /* avoid listener */; i < sessionp->fd_count; i++) {
                if ( send(sessionp->pfds[i].fd,
                          buf, (size_t) num_bytes, 0) == -1 ) {
                    get_err_str(errno, err_str);

M helpers.c => helpers.c +30 -5
@@ 1,3 1,23 @@
/*-
 * helpers.c  Mini Chat Server Helper Functions
 * Copyright (C) 2022  Aritra Sarkar
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * https://www.gnu.org/licenses/gpl-3.0.txt
 */

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>


@@ 20,15 40,20 @@ get_in_addr(struct sockaddr *sa)
void
get_err_str(int errnum, char *err_str)
{
    /* Use `strerror_r()` as a(n) MT-Safe way of retrieving error
     * description corresponding to an `errno` */
    char    buf[STRERROR_BUFLEN];

    /*
     * Use `strerror_r()` as a(n) MT-Safe way of retrieving error
     * description corresponding to an `errno`.
     */

    char buf[STRERROR_BUFLEN];
    if ( !strerror_r(errnum, buf, STRERROR_BUFLEN) )
        strcpy(err_str, buf);
    else
        /* In such rare unfortunate cases when the error reporting
        /*
         * In such rare unfortunate cases when the error reporting
         * tool itself causes an error, dump the error number and let
         * the users figure it out. */
         * the user figure it out.
         */
        sprintf(err_str, "Error number %i", errnum);
}

M helpers.h => helpers.h +2 -2
@@ 7,7 7,7 @@
/* In glibc-2.7, the longest error message string is 50 characters */
# define STRERROR_BUFLEN 128

void *get_in_addr(struct sockaddr *);
void get_err_str(int errnum, char *err_str);
void    *get_in_addr(struct sockaddr *);
void    get_err_str(int errnum, char *err_str);

#endif  /* __MC_HELPERS_H_ */

M main.c => main.c +96 -55
@@ 1,4 1,4 @@
/*
/*-
 * main.c  Mini Chat Server Driver Application
 * Copyright (C) 2022  Aritra Sarkar
 *


@@ 43,39 43,46 @@
#define BACKLOG 10

typedef struct {
    int addr_family;
    char port[8];
    int     addr_family;
    char    port[8];
} prefs_t;

static void signal_handler(int);
static int get_listener(prefs_t *);
static int  get_listener(prefs_t *);
static void set_defaults(prefs_t *);
static void print_usage(const char *program);
static int parse_prefs(int argc, char **argv, prefs_t *);
static int  parse_prefs(int argc, char **argv, prefs_t *);

static void signal_handler(int signum) {
static void
signal_handler(int signum)
{
    printf("\nINFO : Received signal %i.\n", signum);
}

static int get_listener(prefs_t *prefs)
static int
get_listener(prefs_t *prefs)
{
    /* In order to set up a socket connection, we invoke the following functions
    /* 
     * In order to set up a socket connection, we invoke the following functions
     * in this order:
     * 1. getaddrinfo()
     * 2. socket()
     * 3. bind()
     */

    struct addrinfo hints, *res, *p;
    int status, listener=-1, yes;
    char err_str[STRERROR_BUFLEN];
    struct  addrinfo hints, *res, *p;
    int     status, listener=-1, yes;
    char    err_str[STRERROR_BUFLEN];

    /* `memset()` `hints` to zeroes, and set a few necessary fields. This way
       `getaddrinfo()` will be able to set the other necessary fields for us */
    /*
     * `memset()` `hints` to zeroes, and set a few necessary fields.
     * This way `getaddrinfo()` will be able to set the other necessary
     * fields for us.
     */
    memset(&hints, 0, sizeof hints);
    hints.ai_flags = AI_PASSIVE;  /* Use my ip address */
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = prefs->addr_family;
    hints.ai_socktype = SOCK_STREAM;  /* Use TCP */
    hints.ai_socktype = SOCK_STREAM;

    /* set value for SO_REUSEADDR in `setsockopt()` */
    yes = 1;


@@ 93,9 100,12 @@ static int get_listener(prefs_t *prefs)
        return -1;
    }

    /* We'll now need to walk this list. Once we successfully `bind()` to one,
     * we can ignore the rest. In this case, the first one in the list is
     * probably an IPv4 address and the next one is an IPv6 address. */
    /*
     * We'll now need to walk this list. Once we successfully `bind()`
     * to one, we can ignore the rest. In this case, the first one in
     * the list is probably an IPv4 address and the next one is an IPv6
     * address.
     */
    for ( p = res; p; p = p->ai_next ) {
        /* Make sure errno is reset */
        errno = 0;


@@ 107,26 117,36 @@ static int get_listener(prefs_t *prefs)
             * succeed */
            continue;

        /* Reuse the `listener` sockid on restart. Without this the OS might
         * complain about "address already in use" upon restarting the server */
        /*
         * Reuse the `listener` sockid on restart. Without this the OS
         * might complain about "address already in use" upon
         * restarting the server.
         */
        if ( setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,
                        &yes, sizeof yes) == -1 )
            continue;

        /* We now have a listening socket id. Let try `bind()`ing to it */
        /*
         * We now have a listening socket id. Let try `bind()`ing to it
         */
        if ( bind(listener, p->ai_addr, p->ai_addrlen) == -1 )
            /* Let's try binding to the next one in the list */
            continue;

        /* We've got bound. No need to look at rest of the addresses in the
         * list */
        /*
         * We've got bound. No need to look at rest of the addresses in
         * the list.
         */
        break;
    }

    /* Ok, we've walked it and now we don't need it anymore */
    freeaddrinfo(res);

    /* Maybe `p` couldn't bind to any address and ended up walking upto NULL */
    /*
     * Maybe `p` couldn't bind to any address and ended up walking upto
     * NULL.
     */
    if ( !p ) {
        get_err_str(errno, err_str);
        fprintf(stderr, " ERR : %s:%i => Couldn't `bind()` to anything: %s\n",


@@ 145,14 165,17 @@ static int get_listener(prefs_t *prefs)
    return listener;
}

static void set_defaults(prefs_t *prefs)
static void
set_defaults(prefs_t *prefs)
{
    prefs->addr_family = AF_UNSPEC; /* Choose either IPv4 address or
                                     * IPv6 addres, we don't care! */
    /* Choose either IPv4 address or IPv6 addres, we don't care! */
    prefs->addr_family = AF_UNSPEC;

    strcpy(prefs->port, PORT);
}

static void print_usage(const char *program)
static void
print_usage(const char *program)
{
    printf("Usage:  %s [options] [port]\n"
           "Options:\n"


@@ 163,9 186,11 @@ static void print_usage(const char *program)
           program);
}

static int parse_prefs(int argc, char **argv, prefs_t *prefs)
static int
parse_prefs(int argc, char **argv, prefs_t *prefs)
{
    /* Sets user preferences from command line into `prefs`.  Returns a
    /*
     * Sets user preferences from command line into `prefs`.  Returns a
     * positive number indicating the # of arguments parsed (including
     * argv[0]), returns zero in case user requested for  `--help` ||
     * `--version`, returns -1 if arguments weren't recognized.


@@ 175,14 200,19 @@ static int parse_prefs(int argc, char **argv, prefs_t *prefs)
     *        arguments takes precedence over the former.
     */

    int end_of_opts = 0;
    char *program = *argv++;
    int     end_of_opts = 0;
    size_t  offset;
    char    *program;

    program =   *argv++;

    for ( ; *argv; argv++ ) {
    for (; *argv; argv++) {
        if ( **argv == '-' && !end_of_opts ) {
            size_t offset = 1;
            offset = 1;
            if ( strlen(*argv) < 2 ) {
                /* We got got nothing to read from stdin, report err. */
                /*
                 * We got got nothing to read from stdin, report err.
                 */
                fprintf(stderr, "Nothing to read from stdin.\n");
                goto print_usage_ret_error;
            } else if ( strlen(*argv) > 2 ) {


@@ 202,9 232,8 @@ static int parse_prefs(int argc, char **argv, prefs_t *prefs)
                default : goto unrecognized;
                }
            }
        } else {
        } else
            strcpy(prefs->port, *argv);
        }
    }

    return argc;


@@ 229,20 258,26 @@ print_usage_ret_error:
    return -1;
}

int main(int argc, char **argv)
int
main(int argc, char **argv)
{
    int listener, args_parsed, num_events, exit_code;
    char err_str[STRERROR_BUFLEN];
    struct sigaction sa;
    prefs_t prefs;
    session_t session;
    tpool_t tpool;
    int         listener, args_parsed, num_events, exit_code;
    char        err_str[STRERROR_BUFLEN];
    struct      sigaction sa;
    prefs_t     prefs;
    session_t   session;
    tpool_t     tpool;

    /* Set up signal handler for ^C (SIGINT) TODO: and ^\ (SIGQUIT) */
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);  /* No need to block any other signals
                                * while the signal handler runs */

    /*
     * No need to block any other signals while the signal handler
     * runs.
     */
    sigemptyset(&sa.sa_mask);

    if ( sigaction(SIGINT, &sa, NULL) == -1 ) {
        get_err_str(errno, err_str);
        fprintf(stderr, " ERR : %s:%i => `sigaction()`: %s\n",


@@ 287,7 322,8 @@ int main(int argc, char **argv)

    exit_code = EXIT_SUCCESS;   /* Let's hope for the best */

    /* Now we gotta keep `poll()`ing and take care of everything
    /*
     * Now we gotta keep `poll()`ing and take care of everything
     * everytime `poll()` returns until the server receives a SIGINT.
     */



@@ 303,9 339,10 @@ int main(int argc, char **argv)
        if ( (num_events = poll(session.pfds,
                                (nfds_t) session.fd_count, -1)) == -1 ) {
            if ( errno == EINTR ) {
                /* We got Ctrl-C'ed, we need to gracefully release
                 * any allocated memory in heap and then exit */

                /*
                 * We got Ctrl-C'ed, we need to gracefully release any
                 * allocated memory in heap and then exit.
                 */
                printf("DBUG : `poll()` interrupted!\n");
                break;  /* bail out */
            }


@@ 318,8 355,10 @@ int main(int argc, char **argv)
        }

        printf("DBUG : Handling events...\n");
        /* `process_events` lets go of the read lock briefly in order
         * to add work to queue and then locks it back again. */
        /*
         * `process_events()` lets go of the read lock briefly in order
         * to add work to queue and then locks it back again.
         */
        if ( process_events(num_events, &tpool, &session) == -1 ) {
            printf(" ERR : %s:%i => `process_events()`:"
                                  " Failed to handle events!\n",


@@ 338,9 377,11 @@ int main(int argc, char **argv)
        goto destroy_tpool_and_exit;
    }

    /* ^C breaks `poll()` with EINTR which then we use to break the
     * outer infinite while(1) loop and hence we can finally reach this
     * code, do the necessary cleanup and exit gracefully. */
    /*
     * ^C breaks `poll()` with EINTR which then we use to break the
     * outer infinite `while(1)` loop and hence we can finally reach
     * this code, do the necessary cleanup and exit gracefully.
     */

    printf("INFO : Gracefully shutting down...\n");


M response.c => response.c +38 -26
@@ 1,5 1,5 @@
/*
 * response.c  Mini Chat Client Responder
/*-
 * response.c  Mini Chat Server Responder
 * Copyright (C) 2022  Aritra Sarkar
 *
 * This program is free software: you can redistribute it and/or modify


@@ 27,32 27,41 @@
#include "response.h"
#include "session.h"

static inline int append_nick(ResponseCode res_code, const char *nick,
                              char *buf, ssize_t *len, ResponseError *err_code)
static int
append_nick(ResponseCode res_code, const char *nick, char *buf, ssize_t *len,
            ResponseError *err_code)
{
    char    temp[BUFLEN], *msg_ptr;
    size_t  nick_len;

    nick_len    =   strlen(nick);

    if ( !*nick ) {
        if ( err_code )
            *err_code = NO_NICK;
        return -1;
    }

    char temp[BUFLEN], *msg_ptr;
    size_t nick_len = strlen(nick);
    temp[0] = (char) res_code;
    strcpy(temp + 1, nick);         /* User's nick goes first */
    msg_ptr = temp + 1 + nick_len;  /* Get to where nick ends */
    *msg_ptr++ = ' ';               /* Follow nick by a space */

    /* Copy actual message next */
    memcpy(msg_ptr, buf + 1, (size_t) *len - 1);

    temp[0] = (char) res_code;  /* Set resposnse code */
    strcpy(temp + 1, nick); /* Put user's nick first */
    msg_ptr = temp + 1 + nick_len;  /* Get to where user's nick ends */
    *msg_ptr++ = ' ';   /* Put whitespace separator there and move forward */
    memcpy(msg_ptr, buf + 1, (size_t) *len - 1); /* Copy actual message next */
    *len += nick_len + 1;   /* Recompute new length including
                               the nick and a whitespace */
    memcpy(buf, temp, (size_t) *len);    /* Finally copy back everything */
    /* Recompute new length including the nick and a whitespace */
    *len += nick_len + 1;

    /* Finally copy back everything */
    memcpy(buf, temp, (size_t) *len);

    return 0;
}

static inline int nick_exists(const char *nick, const client_t *client,
                              const client_t *clients_list, size_t list_size)
static int
nick_exists(const char *nick, const client_t *client,
            const client_t *clients_list, size_t list_size)
{
    for (size_t i = 0; i < list_size; i++) {
        if ( client != &clients_list[i] &&  /* skip self */


@@ 64,11 73,12 @@ static inline int nick_exists(const char *nick, const client_t *client,
    return -1;
}

static inline int set_nick(SendCode snd_code, client_t *client,
                           const client_t *clients_list, size_t list_size,
                           char *buf, ssize_t *len, ResponseError *err_code)
static inline int
set_nick(SendCode snd_code, client_t *client, const client_t *clients_list,
         size_t list_size, char *buf, ssize_t *len, ResponseError *err_code)
{
    char nick[NICKLEN] = "";
    char    nick[NICKLEN] = "";

    strncpy(nick, buf + 1, (size_t) (NICKLEN < *len ? NICKLEN : *len) - 1);

    if ( !*nick ) {


@@ 98,11 108,14 @@ static inline int set_nick(SendCode snd_code, client_t *client,
    return 0;
}

int respond(client_t *client, const client_t *clients_list, size_t list_size,
            char *buf, ssize_t *len, ResponseError *err_code)
int
respond(client_t *client, const client_t *clients_list, size_t list_size,
        char *buf, ssize_t *len, ResponseError *err_code)
{
    SendCode    snd_code;

    /* Extract the first byte as the send code */
    SendCode snd_code = (SendCode) buf[0];
    snd_code    =   (SendCode) buf[0];

    /* Assume everything's gonna be fine */
    if ( err_code )


@@ 114,9 127,8 @@ int respond(client_t *client, const client_t *clients_list, size_t list_size,

    case SND_JOIN:
    case SND_SETNICK:
        return set_nick(snd_code, client,
                        clients_list, list_size,
                        buf, len, err_code);
        return set_nick(snd_code, client, clients_list, list_size, buf, len,
                        err_code);

    case SND_ACTION:
        return append_nick(RES_ACTION, client->nick, buf, len, err_code);

M session.c => session.c +9 -7
@@ 1,4 1,4 @@
/*
/*-
 * session.c  Mini Chat Server Session Manager
 * Copyright (C) 2022  Aritra Sarkar
 *


@@ 31,11 31,11 @@
int
session_init(session_t *sessionp, int listener)
{
    char err_str[STRERROR_BUFLEN];
    char    err_str[STRERROR_BUFLEN];

    /* Let's set up polling, we start with a room of 2 clients + the
     * listening server, so 3. As clients join, we'll dynamically grow
     * to accomodate for them */
     * to accomodate for them. */
    sessionp->fd_count = 0;
    sessionp->fd_size = 3;



@@ 83,7 83,7 @@ ret_err:
int
add_client(int cfd, session_t *sessionp)
{
    char err_str[STRERROR_BUFLEN];
    char    err_str[STRERROR_BUFLEN];

    if ( sessionp->fd_count == sessionp->fd_size ) {
        /* We've run out of room, double `fd_size` and `realloc()` */


@@ 119,14 119,16 @@ add_client(int cfd, session_t *sessionp)
int
remove_client(size_t index, session_t *sessionp)
{
    /* We remove a client by copying over details of the last client
    /*
     * We remove a client by copying over details of the last client
     * in the queue over the client we want to remove, thus
     * overwriting the details of the client we want to remove.
     *
     * However, if it we choose to remove the last client in the
     * queue, we don't need to copy shit over, just reduce the count
     * by one and it will be overwritten by the next connecting
     * client. */
     * client.
     */

    if ( index >= sessionp->fd_count )
        return -1;


@@ 145,7 147,7 @@ remove_client(size_t index, session_t *sessionp)
int
session_destroy(session_t *sessionp)
{
    char err_str[STRERROR_BUFLEN];
    char    err_str[STRERROR_BUFLEN];

    free(sessionp->pfds);
    free(sessionp->clients);

M session.h => session.h +5 -5
@@ 6,15 6,15 @@
# include "common.h"

typedef struct client {
    char nick[NICKLEN];
    char    nick[NICKLEN];
    /* We'll probably have more stuff here */
} client_t;

typedef struct session {
    size_t fd_size, fd_count;
    struct pollfd *pfds;
    client_t *clients;
    pthread_rwlock_t lock;
    size_t              fd_size, fd_count;
    struct pollfd       *pfds;
    client_t            *clients;
    pthread_rwlock_t    lock;
} session_t;

int session_init(session_t *, int listener);

M tpool.c => tpool.c +28 -15
@@ 1,4 1,4 @@
/*
/*-
 * tpool.c  Mini Chat Thread Pool Manager
 *
 *          A lot of this code is just some modification over the


@@ 142,9 142,11 @@ tpool_thread(void *tpoolp_vp)
    int             thread_id, i;
    char            err_str[STRERROR_BUFLEN];

    thread_id = -1; /* Just to silence `-Wsometimes-uninitialized`.
                     * The for loop below will always succeed for one
                     * of the threads in `tpoolp->threads` */
    /*
     * Just to silence `-Wsometimes-uninitialized`. The for loop below
     * will always succeed for one of the threads in `tpoolp->threads`.
     */
    thread_id = -1;

    /* thread id is just the index of this thread in tpoolp->threads */
    for (i = 0; (size_t) i < tpoolp->num_threads; i++) {


@@ 156,9 158,11 @@ tpool_thread(void *tpoolp_vp)
    }

    if ( thread_id == -1 ) {
        /* if this function was executed by a thread not in
        /*
         * if this function was executed by a thread not in
         * `tpoolp->threads`, then it was called externally
         * and hence we have no reason to continue living. */
         * and hence we have no reason to continue living.
         */
        printf(" ERR : `tpool_thread()` defined in %s:%i was called"
                     " external to a thread pool.\n"
               "     : Thread exiting...\n", __FILE__, __LINE__ - 26);


@@ 183,9 187,11 @@ tpool_thread(void *tpoolp_vp)

        while ( !tpoolp->cur_queue_size && !tpoolp->shutdown ) {

            /* If no work is in queue and no shutdown is in progress,
            /*
             * If no work is in queue and no shutdown is in progress,
             * release `queue_lock` and wait until we are awakened by
             * `tpool_add_work()` or `tpool_destroy()` */
             * `tpool_add_work()` or `tpool_destroy()`.
             */

            if ( pthread_cond_wait(&tpoolp->queue_not_empty,
                                   &tpoolp->queue_lock) ) {


@@ 199,8 205,10 @@ tpool_thread(void *tpoolp_vp)

        printf("DBUG : [THREAD #%02i] Woke up...\n", thread_id);

        /* Were we awakened by `tpool_destroy()`? If so, a shutdown
         * might be in progress and hence we must exit */
        /*
         * Were we awakened by `tpool_destroy()`? If so, a shutdown
         * might be in progress and hence we must exit.
         */
        if ( tpoolp->shutdown ) {
            printf("DBUG : [THREAD #%02i] Acknowledged shutdown!\n",
                   thread_id);


@@ 214,10 222,12 @@ tpool_thread(void *tpoolp_vp)
        else
            tpoolp->queue_head = workp->next;

        /* If `do_not_block_when_full` is false, `tpool_add_work` is
        /*
         * If `do_not_block_when_full` is false, `tpool_add_work` is
         * going to block if the queue is full. Since we dequeued a
         * work from the queue, wake those threads up so it can enqueue
         * its work now. */
         * its work now.
         */
        if ( !tpoolp->do_not_block_when_full &&
              tpoolp->cur_queue_size == tpoolp->max_queue_size - 1 ) {
            if ( pthread_cond_broadcast(&tpoolp->queue_not_full) ) {


@@ 229,11 239,13 @@ tpool_thread(void *tpoolp_vp)
            }
        }

        /* When a ``graceful'' shutdown is in progress,
        /*
         * When a ``graceful'' shutdown is in progress,
         * `tpool_destroy()` will be waiting until the work queue is
         * empty. If we dequeued the last work, we can now wake up
         * `tpool_destroy()` and let it continue with the shutdown
         * process. */
         * process.
         */
        if ( !tpoolp->cur_queue_size ) {
            if ( pthread_cond_signal(&tpoolp->queue_empty) ) {
                get_err_str(errno, err_str);


@@ 294,7 306,8 @@ tpool_add_work(tpool_t *tpoolp, void (*routine)(int, void *), void *arg)
        goto free_and_ret_err;
    }

    /* if queue is full and we don't want to block on new work
    /*
     * if queue is full and we don't want to block on new work
     * additions, we'll simply return with an error.
     *
     * TODO: We can have a different return code for this should we