Server config load and graceful client exit
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
build
|
build
|
||||||
|
.conf
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -Wall -I.
|
CFLAGS = -Wall -I.
|
||||||
|
LDFLAGS = -lconfig
|
||||||
|
|
||||||
all: client server
|
all: client server
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ client:
|
|||||||
|
|
||||||
server:
|
server:
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
$(CC) $(CFLAGS) -o build/server server.c util/util.c
|
$(CC) $(CFLAGS) -o build/server server.c util/util.c $(LDFLAGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f client server
|
rm -f client server
|
||||||
|
|||||||
@@ -2,12 +2,21 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/msg.h>
|
#include <sys/msg.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
|
#include "signal.h"
|
||||||
|
|
||||||
|
// Wanted a graceful exit so need to keep this in global state
|
||||||
|
static int client_queue_id_global = -1;
|
||||||
|
static int server_queue_id_global = -1;
|
||||||
|
static char client_id_global[COMMAND_LENGTH];
|
||||||
|
static pid_t heartbeat_pid = -1;
|
||||||
|
static pid_t input_pid = -1;
|
||||||
|
|
||||||
// Make a macro for this cause I'm not typing sizeof twice every time
|
// Make a macro for this cause I'm not typing sizeof twice every time
|
||||||
#define send(queue_id, msgbuf_ptr) msgsnd(queue_id, msgbuf_ptr, sizeof(*(msgbuf_ptr)) - sizeof(long), 0)
|
#define send(queue_id, msgbuf_ptr) msgsnd(queue_id, msgbuf_ptr, sizeof(*(msgbuf_ptr)) - sizeof(long), 0)
|
||||||
@@ -30,6 +39,27 @@ static void cleanup_queue(int qid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cleanup_and_exit(int sig) {
|
||||||
|
printf("\nLogging out... ");
|
||||||
|
|
||||||
|
if (server_queue_id_global != -1) {
|
||||||
|
msgbuf_t logout = {.mtype = Logout, .sender = ""};
|
||||||
|
strncpy(logout.sender, client_id_global, COMMAND_LENGTH - 1);
|
||||||
|
msgsnd(server_queue_id_global, &logout, sizeof(logout) - sizeof(long), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Burn the orphanage
|
||||||
|
if (heartbeat_pid > 0)
|
||||||
|
kill(heartbeat_pid, SIGTERM);
|
||||||
|
if (input_pid > 0)
|
||||||
|
kill(input_pid, SIGTERM);
|
||||||
|
|
||||||
|
cleanup_queue(client_queue_id_global);
|
||||||
|
|
||||||
|
printf("Bye!\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
static void input_loop(int server_queue_id, const char *client_id) {
|
static void input_loop(int server_queue_id, const char *client_id) {
|
||||||
while (1) {
|
while (1) {
|
||||||
// Minimum length plus extra space fore command name and seperators
|
// Minimum length plus extra space fore command name and seperators
|
||||||
@@ -88,9 +118,8 @@ static void input_loop(int server_queue_id, const char *client_id) {
|
|||||||
}
|
}
|
||||||
} else if (strncmp(buffer, "/mute ", 6) == 0) {
|
} else if (strncmp(buffer, "/mute ", 6) == 0) {
|
||||||
// handle mute
|
// handle mute
|
||||||
} else if (strcmp(buffer, "/quit") == 0) {
|
} else if (strcmp(buffer, "/quit") == 0 || strcmp(buffer, "/exit") == 0 || strcmp(buffer, "/q") == 0) {
|
||||||
// TODO: Would be nice to have a way to gracefully logout and not just
|
return;
|
||||||
// depend on heartbeats, maybe
|
|
||||||
} else if (strncmp(buffer, "/inbox", 8) == 0) {
|
} else if (strncmp(buffer, "/inbox", 8) == 0) {
|
||||||
msgbuf_t msg = {.mtype = Inbox, .sender = ""};
|
msgbuf_t msg = {.mtype = Inbox, .sender = ""};
|
||||||
strncpy(msg.sender, client_id, COMMAND_LENGTH - 1);
|
strncpy(msg.sender, client_id, COMMAND_LENGTH - 1);
|
||||||
@@ -163,16 +192,29 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
printf("Login accepted. Welcome, %s!\n", resp.command);
|
printf("Login accepted. Welcome, %s!\n", resp.command);
|
||||||
|
|
||||||
if (fork() == 0) {
|
strncpy(client_id_global, client_id, COMMAND_LENGTH - 1);
|
||||||
|
client_queue_id_global = client_queue_id;
|
||||||
|
server_queue_id_global = server_queue_id;
|
||||||
|
|
||||||
|
|
||||||
|
heartbeat_pid = fork();
|
||||||
|
if (heartbeat_pid == 0) {
|
||||||
heartbeat_loop(server_queue_id, client_id);
|
heartbeat_loop(server_queue_id, client_id);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fork() == 0) {
|
input_pid = fork();
|
||||||
|
if (input_pid == 0) {
|
||||||
input_loop(server_queue_id, client_id);
|
input_loop(server_queue_id, client_id);
|
||||||
|
// The /quit command just returns from the input loop
|
||||||
|
kill(getppid(), SIGTERM);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup them below the forks to avoid multiple triggers
|
||||||
|
signal(SIGINT, cleanup_and_exit);
|
||||||
|
signal(SIGTERM, cleanup_and_exit);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
msgbuf_t incoming;
|
msgbuf_t incoming;
|
||||||
ssize_t got = msgrcv(client_queue_id, &incoming, sizeof(incoming) - sizeof(long), 0, 0);
|
ssize_t got = msgrcv(client_queue_id, &incoming, sizeof(incoming) - sizeof(long), 0, 0);
|
||||||
@@ -194,7 +236,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// Check the command field for the list type
|
// Check the command field for the list type
|
||||||
if (strncmp(incoming.command, "active", COMMAND_LENGTH) == 0) {
|
if (strncmp(incoming.command, "active", COMMAND_LENGTH) == 0) {
|
||||||
printf("Active clients:\n%s\n", incoming.message);
|
printf("Active clients:\n%s\n", incoming.message);
|
||||||
} else if (strncmp(incoming.command, "group", COMMAND_LENGTH) == 0) {
|
} else if (strncmp(incoming.command, "groups", COMMAND_LENGTH) == 0) {
|
||||||
printf("All group members:\n%s\n", incoming.message);
|
printf("All group members:\n%s\n", incoming.message);
|
||||||
} else {
|
} else {
|
||||||
printf("All clients:\n%s\n", incoming.message);
|
printf("All clients:\n%s\n", incoming.message);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Server configuration file
|
||||||
|
|
||||||
|
clients = (
|
||||||
|
{
|
||||||
|
client_id = "test1";
|
||||||
|
nickname = "Kregielnia";
|
||||||
|
},
|
||||||
|
{
|
||||||
|
client_id = "test2";
|
||||||
|
nickname = "Bajzel";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
groups = (
|
||||||
|
{
|
||||||
|
name = "HelloChat";
|
||||||
|
members = ["test1", "test2"];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "Macarena";
|
||||||
|
members = [];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "Bananownia";
|
||||||
|
members = ["test2"];
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "commands.h"
|
// commands.h already is included by util.h
|
||||||
#include "util/util.h"
|
#include "util/util.h"
|
||||||
|
|
||||||
#define send(queue_id, msgbuf_ptr) msgsnd(queue_id, msgbuf_ptr, sizeof(*(msgbuf_ptr)) - sizeof(long), 0)
|
#define send(queue_id, msgbuf_ptr) msgsnd(queue_id, msgbuf_ptr, sizeof(*(msgbuf_ptr)) - sizeof(long), 0)
|
||||||
@@ -68,32 +68,57 @@ static void handle_hearbeat_timeouts(Client_t *clients, int client_count, int se
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
// TODO: Load config file with clients and groups
|
server_config_t config;
|
||||||
int client_count = 2;
|
// If we are given a path, try to load the config from there
|
||||||
int group_count = 3;
|
if (argc >= 2) {
|
||||||
|
load_config(&config, argv[1]);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "No config file provided, using default location example.conf\n");
|
||||||
|
load_config(&config, "./example.conf");
|
||||||
|
}
|
||||||
|
|
||||||
|
int client_count = config.client_count;
|
||||||
|
int group_count = config.group_count;
|
||||||
int clients_memory_id = shmget(IPC_PRIVATE, sizeof(Client_t) * client_count, IPC_CREAT | 0666);
|
int clients_memory_id = shmget(IPC_PRIVATE, sizeof(Client_t) * client_count, IPC_CREAT | 0666);
|
||||||
Client_t *clients = shmat(clients_memory_id, NULL, 0);
|
Client_t *clients = shmat(clients_memory_id, NULL, 0);
|
||||||
Group_t *groups = malloc(sizeof(Group_t) * 3);
|
Group_t *groups = malloc(sizeof(Group_t) * group_count);
|
||||||
|
|
||||||
int semaphore_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
|
int semaphore_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
|
||||||
semctl(semaphore_id, 0, SETVAL, 1);
|
semctl(semaphore_id, 0, SETVAL, 1);
|
||||||
|
|
||||||
clients[0] =
|
for (int i = 0; i < client_count; i++) {
|
||||||
(Client_t){.id = "test1", .logged_in = false, .muted_clients = NULL, .nickname = "Kregielnia", .last_seen = 0};
|
strncpy(clients[i].id, config.clients[i].client_id, COMMAND_LENGTH - 1);
|
||||||
clients[1] =
|
clients[i].logged_in = false;
|
||||||
(Client_t){.id = "test2", .logged_in = false, .muted_clients = NULL, .nickname = "Bajzel", .last_seen = 0};
|
clients[i].muted_clients = NULL;
|
||||||
|
strncpy(clients[i].nickname, config.clients[i].nickname, COMMAND_LENGTH - 1);
|
||||||
|
clients[i].queue_id = -1;
|
||||||
|
clients[i].last_seen = 0;
|
||||||
|
clients[i].saved_message_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
groups[0] = (Group_t){.name = "HelloChat", .client_count = 2, .members = NULL};
|
for (int i = 0; i < group_count; i++) {
|
||||||
groups[0].members = malloc(sizeof(Client_t *) * client_count);
|
strncpy(groups[i].name, config.groups[i].name, COMMAND_LENGTH - 2);
|
||||||
groups[0].members[0] = &clients[0];
|
groups[i].client_count = config.groups[i].member_count;
|
||||||
groups[0].members[1] = &clients[1];
|
groups[i].members = malloc(sizeof(Client_t *) * config.groups[i].member_count);
|
||||||
|
for (int j = 0; j < config.groups[i].member_count; j++) {
|
||||||
|
// Find the client with the given id
|
||||||
|
for (int k = 0; k < client_count; k++) {
|
||||||
|
if (strncmp(clients[k].id, config.groups[i].member_ids[j], COMMAND_LENGTH) == 0) {
|
||||||
|
groups[i].members[j] = &clients[k];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
groups[1] = (Group_t){.name = "Macarena", .client_count = 0, .members = NULL};
|
// No longer useful after copying the data over
|
||||||
groups[1].members = malloc(sizeof(Client_t *) * client_count);
|
for (int i = 0; i < group_count; i++) {
|
||||||
|
for (int j = 0; j < config.groups[i].member_count; j++) {
|
||||||
groups[2] = (Group_t){.name = "Bananownia", .client_count = 1, .members = NULL};
|
free(config.groups[i].member_ids[j]);
|
||||||
groups[2].members = malloc(sizeof(Client_t *) * client_count);
|
}
|
||||||
groups[2].members[0] = &clients[1];
|
}
|
||||||
|
free(config.clients);
|
||||||
|
free(config.groups);
|
||||||
|
|
||||||
int server_queue_id = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
|
int server_queue_id = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
|
||||||
|
|
||||||
@@ -119,6 +144,8 @@ int main(int argc, char **argv) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("Server ready.\n");
|
||||||
|
|
||||||
msgbuf_t msgbuf;
|
msgbuf_t msgbuf;
|
||||||
while (1) {
|
while (1) {
|
||||||
// Read from the ipc, then handle the command
|
// Read from the ipc, then handle the command
|
||||||
@@ -305,6 +332,15 @@ int main(int argc, char **argv) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Logout: {
|
case Logout: {
|
||||||
|
for (int i = 0; i < client_count; i++) {
|
||||||
|
if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0 && clients[i].logged_in) {
|
||||||
|
clients[i].logged_in = false;
|
||||||
|
clients[i].queue_id = -1;
|
||||||
|
printf("Client %s logged out\n", msgbuf.sender);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case Hearbeat:
|
case Hearbeat:
|
||||||
for (int i = 0; i < client_count; i++) {
|
for (int i = 0; i < client_count; i++) {
|
||||||
|
|||||||
+97
@@ -1,5 +1,12 @@
|
|||||||
|
#include "fcntl.h"
|
||||||
|
#include "unistd.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <libconfig.h>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
static char alphs[] = {'-', '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
|
static char alphs[] = {'-', '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
|
||||||
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
@@ -22,3 +29,93 @@ void get_id(char *id_buf) {
|
|||||||
}
|
}
|
||||||
id_buf[15] = '\0';
|
id_buf[15] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load configuration using libconfig
|
||||||
|
void load_config(server_config_t *cfg, const char *path) {
|
||||||
|
config_t config;
|
||||||
|
config_init(&config);
|
||||||
|
|
||||||
|
if (!config_read_file(&config, path)) {
|
||||||
|
fprintf(stderr, "Error reading config file %s:%d - %s\n",
|
||||||
|
config_error_file(&config),
|
||||||
|
config_error_line(&config),
|
||||||
|
config_error_text(&config));
|
||||||
|
config_destroy(&config);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read clients
|
||||||
|
config_setting_t *clients = config_lookup(&config, "clients");
|
||||||
|
if (!clients) {
|
||||||
|
fprintf(stderr, "No 'clients' section found in config file\n");
|
||||||
|
config_destroy(&config);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int client_count = config_setting_length(clients);
|
||||||
|
cfg->client_count = client_count;
|
||||||
|
cfg->clients = malloc(sizeof(client_config_t) * client_count);
|
||||||
|
|
||||||
|
for (int i = 0; i < client_count; i++) {
|
||||||
|
config_setting_t *client = config_setting_get_elem(clients, i);
|
||||||
|
const char *client_id, *nickname;
|
||||||
|
|
||||||
|
if (!config_setting_lookup_string(client, "client_id", &client_id) ||
|
||||||
|
!config_setting_lookup_string(client, "nickname", &nickname)) {
|
||||||
|
fprintf(stderr, "Error reading client %d configuration\n", i);
|
||||||
|
config_destroy(&config);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(cfg->clients[i].client_id, client_id, COMMAND_LENGTH - 1);
|
||||||
|
cfg->clients[i].client_id[COMMAND_LENGTH - 1] = '\0';
|
||||||
|
strncpy(cfg->clients[i].nickname, nickname, COMMAND_LENGTH - 1);
|
||||||
|
cfg->clients[i].nickname[COMMAND_LENGTH - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read groups
|
||||||
|
config_setting_t *groups = config_lookup(&config, "groups");
|
||||||
|
if (!groups) {
|
||||||
|
fprintf(stderr, "No 'groups' section found in config file\n");
|
||||||
|
config_destroy(&config);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int group_count = config_setting_length(groups);
|
||||||
|
cfg->group_count = group_count;
|
||||||
|
cfg->groups = malloc(sizeof(group_config_t) * group_count);
|
||||||
|
|
||||||
|
for (int i = 0; i < group_count; i++) {
|
||||||
|
config_setting_t *group = config_setting_get_elem(groups, i);
|
||||||
|
const char *name;
|
||||||
|
config_setting_t *members;
|
||||||
|
|
||||||
|
if (!config_setting_lookup_string(group, "name", &name)) {
|
||||||
|
fprintf(stderr, "Error reading group %d name\n", i);
|
||||||
|
config_destroy(&config);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(cfg->groups[i].name, name, COMMAND_LENGTH - 2);
|
||||||
|
cfg->groups[i].name[COMMAND_LENGTH - 2] = '\0';
|
||||||
|
|
||||||
|
members = config_setting_get_member(group, "members");
|
||||||
|
if (!members) {
|
||||||
|
fprintf(stderr, "Error reading group %d members\n", i);
|
||||||
|
config_destroy(&config);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int member_count = config_setting_length(members);
|
||||||
|
cfg->groups[i].member_count = member_count;
|
||||||
|
|
||||||
|
for (int j = 0; j < member_count; j++) {
|
||||||
|
const char *member_id = config_setting_get_string_elem(members, j);
|
||||||
|
cfg->groups[i].member_ids[j] = malloc(COMMAND_LENGTH);
|
||||||
|
strncpy(cfg->groups[i].member_ids[j], member_id, COMMAND_LENGTH - 1);
|
||||||
|
cfg->groups[i].member_ids[j][COMMAND_LENGTH - 1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config_destroy(&config);
|
||||||
|
}
|
||||||
|
|||||||
+22
@@ -1 +1,23 @@
|
|||||||
|
#include "../commands.h"
|
||||||
|
|
||||||
void get_id(char *id_buf);
|
void get_id(char *id_buf);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char client_id[COMMAND_LENGTH];
|
||||||
|
char nickname[COMMAND_LENGTH];
|
||||||
|
} client_config_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[COMMAND_LENGTH - 1];
|
||||||
|
char *member_ids[COMMAND_LENGTH]; // Array of client IDs
|
||||||
|
int member_count;
|
||||||
|
} group_config_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int client_count;
|
||||||
|
int group_count;
|
||||||
|
client_config_t *clients;
|
||||||
|
group_config_t *groups;
|
||||||
|
} server_config_t;
|
||||||
|
|
||||||
|
void load_config(server_config_t *config, const char *path);
|
||||||
|
|||||||
Reference in New Issue
Block a user