From 7dde974398b24b69f3eb99259bc0b308ea196c0f Mon Sep 17 00:00:00 2001 From: Yanis Andry Date: Fri, 30 Jan 2026 16:34:35 +0100 Subject: [PATCH 1/5] feat: improve Makefile output's and macros (bonus / debug) --- Makefile | 147 ++++++++++++++++++++++++++++++++++++++------------ srcs/main.cpp | 4 ++ 2 files changed, 117 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index 77d1a14..424bc09 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,30 @@ -NAME = ircserv +NAME := ircserv +ANNOUNCER_NAME := Announcer -CXX = c++ +CXX := c++ +CXXFLAGS := -Wall -Wextra -Werror -std=c++98 -MMD -MP -SRCS_DIR = ./srcs -INC_DIR = ./incs -OBJ_DIR = ./objs -TEST_DIR = ./tests +SRCS_DIR := ./srcs +INC_DIR := ./incs +OBJ_DIR := ./objs +TEST_DIR := ./tests - -INCLUDES = -I$(INC_DIR) \ +INCLUDES := -I$(INC_DIR) \ -I$(INC_DIR)/core \ -I$(INC_DIR)/network \ - -I$(INC_DIR)/commands -# -I$(INC_DIR)/bot + -I$(INC_DIR)/commands + +BONUS_INCLUDES := -I$(INC_DIR)/bot + +get_color = $(if $(filter Purple,$(1)),$(shell tput setaf 5),$(if $(filter Red,$(1)),$(shell tput setaf 1),$(if $(filter Cyan,$(1)),$(shell tput setaf 6),$(if $(filter Blue,$(1)),$(shell tput setaf 4),$(if $(filter Yellow,$(1)),$(shell tput setaf 3),$(if $(filter Green,$(1)),$(shell tput setaf 2),$(if $(filter Off,$(1)),$(shell tput sgr0),$(shell tput sgr0)))))))) -FLAGS = -Wall -Wextra -Werror -std=c++98 -g3 $(INCLUDES) +ifdef DEBUG + CXXFLAGS += -g3 -DDEBUG_MODE +else ifdef FSAN + CXXFLAGS += -g3 -fsanitize=address -DDEBUG_MODE +endif -SRCS = $(SRCS_DIR)/main.cpp \ +SRCS := $(SRCS_DIR)/main.cpp \ $(SRCS_DIR)/core/Config.cpp \ $(SRCS_DIR)/core/Server.cpp \ $(SRCS_DIR)/core/Client.cpp \ @@ -50,19 +58,103 @@ SRCS = $(SRCS_DIR)/main.cpp \ $(SRCS_DIR)/modes/OperatorMode.cpp \ $(SRCS_DIR)/modes/KeyMode.cpp \ $(SRCS_DIR)/modes/UserLimitMode.cpp \ - $(SRCS_DIR)/protocol/IrcUtils.cpp -# $(SRCS_DIR)/bot/BotClient.cpp - -OBJ = $(SRCS:$(SRCS_DIR)/%.cpp=$(OBJ_DIR)/%.o) + $(SRCS_DIR)/protocol/IrcUtils.cpp + +BONUS_SRCS := $(SRCS_DIR)/bot/BotClient.cpp \ + $(SRCS_DIR)/bot/SixSevenBot.cpp + +OBJS := $(SRCS:$(SRCS_DIR)/%.cpp=$(OBJ_DIR)/%.o) +OBJS_BONUS := $(SRCS:$(SRCS_DIR)/%.cpp=$(OBJ_DIR)/bonus/%.o) \ + $(BONUS_SRCS:$(SRCS_DIR)/%.cpp=$(OBJ_DIR)/bonus/%.o) + +BONUS_MARKER := .bonus -all: $(NAME) +.PHONY: all bonus clean fclean re debug fsan format lint lint-fix \ + test test_run test_filter docker-test docker-test-shell docker-test-filter -$(NAME): $(OBJ) - $(CXX) $(FLAGS) -o $(NAME) $(OBJ) +all: + @if $(MAKE) --no-print-directory -q $(NAME) 2>/dev/null; then \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] Everything is up to date. $(call get_color,Purple)Zzz...$(call get_color,Off)"; \ + else \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] Starting build of $(call get_color,Purple)$(NAME)$(call get_color,Off)..."; \ + $(MAKE) --no-print-directory $(NAME); \ + fi + +bonus: + @if [ -f $(BONUS_MARKER) ] && $(MAKE) --no-print-directory -q $(BONUS_MARKER) 2>/dev/null; then \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] Bonus is already up to date. $(call get_color,Purple)Zzz...$(call get_color,Off)"; \ + else \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] Starting build of $(call get_color,Yellow)$(NAME)$(call get_color,Off) (with bots. many of these.)..."; \ + $(MAKE) --no-print-directory $(BONUS_MARKER); \ + fi + +-include $(OBJS:.o=.d) +-include $(OBJS_BONUS:.o=.d) $(OBJ_DIR)/%.o: $(SRCS_DIR)/%.cpp - @mkdir -p $(dir $@) - $(CXX) $(FLAGS) -c $< -o $@ + @mkdir -p $(dir $@) + @printf "\r\033[K$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Cyan)Compiling $<$(call get_color,Off)" + @$(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ || \ + (echo "\n$(call get_color,Red)Compilation failed for $<$(call get_color,Off)" && exit 1) + +$(OBJ_DIR)/bonus/%.o: $(SRCS_DIR)/%.cpp + @mkdir -p $(dir $@) + @printf "\r\033[K$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Cyan)Compiling $<$(call get_color,Off)" + @$(CXX) $(CXXFLAGS) -DBONUS $(INCLUDES) $(BONUS_INCLUDES) -c $< -o $@ || \ + (echo "\n$(call get_color,Red)Compilation failed for $<$(call get_color,Off)" && exit 1) + +$(NAME): $(OBJS) + @printf "\r\033[K$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Green)Linking $(NAME)$(call get_color,Off)\n" + @$(CXX) $(CXXFLAGS) $(INCLUDES) -o $(NAME) $(OBJS) || \ + (echo "$(call get_color,Red)Linking failed!$(call get_color,Off)" && exit 1) + @if [ "$(DEBUG)" = "1" ]; then \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) compiled in $(call get_color,Red)DEBUG$(call get_color,Off) mode!"; \ + else \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) has been compiled!"; \ + fi + +$(BONUS_MARKER): $(OBJS_BONUS) + @printf "\r\033[K$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Green)Linking $(NAME) with bonus$(call get_color,Off)\n" + @$(CXX) $(CXXFLAGS) $(INCLUDES) $(BONUS_INCLUDES) -o $(NAME) $(OBJS_BONUS) || \ + (echo "$(call get_color,Red)Linking failed!$(call get_color,Off)" && exit 1) + @touch $(BONUS_MARKER) + @if [ "$(DEBUG)" = "1" ]; then \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) with $(call get_color,Yellow)BONUS$(call get_color,Off) compiled in $(call get_color,Red)DEBUG$(call get_color,Off) mode!"; \ + else \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) with $(call get_color,Yellow)BONUS$(call get_color,Off) has been compiled!"; \ + fi + +clean: + @echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] Cleaning object files..." + @if [ -d $(OBJ_DIR) ]; then \ + rm -rf $(OBJ_DIR) && \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] Removed object and dependency files"; \ + else \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] No object files to clean"; \ + fi + @if [ -f $(BONUS_MARKER) ]; then \ + rm -f $(BONUS_MARKER) && \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] Removed bonus marker"; \ + fi + @$(MAKE) --no-print-directory -C $(TEST_DIR) clean + +fclean: clean + @if [ -f $(NAME) ]; then \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] Removing $(call get_color,Purple)$(NAME)$(call get_color,Off)..."; \ + rm -f $(NAME) && \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) is GONE!!"; \ + else \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) is already gone"; \ + fi + @$(MAKE) --no-print-directory -C $(TEST_DIR) fclean + +re: fclean all + +debug: + @$(MAKE) --no-print-directory re DEBUG=1 + +fsan: + @$(MAKE) --no-print-directory re FSAN=1 test: @$(MAKE) -C $(TEST_DIR) @@ -82,17 +174,6 @@ docker-test-shell: docker-test-filter: @$(MAKE) -C $(TEST_DIR) docker-test-filter FILTER=$(FILTER) -clean: - rm -f $(OBJ) - rm -rf $(OBJ_DIR) - $(MAKE) -C $(TEST_DIR) clean - -fclean: clean - rm -f $(NAME) - $(MAKE) -C $(TEST_DIR) fclean - -re: fclean all - format: @find . -type f \( -name "*.cpp" -o -name "*.hpp" \) -exec clang-format -i {} + @@ -101,5 +182,3 @@ lint: lint-fix: run-clang-tidy -fix - -.PHONY: all clean fclean re format lint lint-fix test test_run test_filter docker-test docker-test-shell docker-test-filter diff --git a/srcs/main.cpp b/srcs/main.cpp index 213e602..1abecd5 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -16,7 +16,11 @@ int main(int argc, char** argv) std::signal(SIGINT, signalHandler); std::signal(SIGTERM, signalHandler); +#ifdef DEBUG_MODE Logger::getInstance().setMinLevel(Logger::DEBUG); +#else + Logger::getInstance().setMinLevel(Logger::NOTICE); +#endif try { From 9b6df7658848042e3e33ea405a4d039d179a7715 Mon Sep 17 00:00:00 2001 From: Yanis Andry Date: Fri, 30 Jan 2026 16:41:52 +0100 Subject: [PATCH 2/5] feat: Logger auto-determines whether it should write in stdout or stderr --- srcs/core/Logger.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/srcs/core/Logger.cpp b/srcs/core/Logger.cpp index c5dd334..fbfbf4f 100644 --- a/srcs/core/Logger.cpp +++ b/srcs/core/Logger.cpp @@ -106,8 +106,9 @@ void Logger::flush() { if (m_lineStarted && m_currentLevel >= m_minLevel) { - *m_output << m_buffer.str() << "\033[0m" << '\n'; - m_output->flush(); + std::ostream& out = (m_currentLevel >= ERROR) ? std::cerr : *m_output; + out << m_buffer.str() << "\033[0m" << '\n'; + out.flush(); } m_buffer.str(""); m_buffer.clear(); From aa4aee4f59a8674e2e22ddb6b9ed4a7adb23f7cc Mon Sep 17 00:00:00 2001 From: Yanis Andry Date: Fri, 30 Jan 2026 16:46:51 +0100 Subject: [PATCH 3/5] fix: quitting wasn't actually disconnecting from the server :) --- incs/core/IServer.hpp | 1 + incs/core/Server.hpp | 1 + srcs/commands/QuitCommand.cpp | 5 +++++ srcs/core/Server.cpp | 8 ++++++++ tests/mocks/Server.cpp | 5 +++++ tests/mocks/Server.hpp | 1 + 6 files changed, 21 insertions(+) diff --git a/incs/core/IServer.hpp b/incs/core/IServer.hpp index 08cbc61..db11633 100644 --- a/incs/core/IServer.hpp +++ b/incs/core/IServer.hpp @@ -25,4 +25,5 @@ class IServer virtual void deleteChannelIfEmpty(IChannel* channel) = 0; virtual size_t getChannelCount() const = 0; virtual std::string getServerName() const = 0; + virtual void markForDisconnect(int fd) = 0; }; diff --git a/incs/core/Server.hpp b/incs/core/Server.hpp index dc2c0d2..e9c8077 100644 --- a/incs/core/Server.hpp +++ b/incs/core/Server.hpp @@ -83,4 +83,5 @@ class Server : public IServer size_t getChannelCount() const; std::string getServerName() const; bool requiresPassword() const; + void markForDisconnect(int fd); }; diff --git a/srcs/commands/QuitCommand.cpp b/srcs/commands/QuitCommand.cpp index 3259734..92e1cef 100644 --- a/srcs/commands/QuitCommand.cpp +++ b/srcs/commands/QuitCommand.cpp @@ -2,6 +2,7 @@ #include "IChannel.hpp" #include "IClient.hpp" #include "IServer.hpp" +#include "core/IMessageBuffer.hpp" #include "commands/ACommand.hpp" #include "commands/CommandRegistration.hpp" #include "commands/CommandType.hpp" @@ -62,6 +63,10 @@ void QuitCommand::doExecute(IClient* client, const Message& message) m_server.deleteChannelIfEmpty(*it); } } + + std::string errorMsg = "ERROR :Closing link (" + quitMessage + ")\r\n"; + client->getBuffer().appendWrite(errorMsg); + m_server.markForDisconnect(client->getFd()); } ACommand* QuitCommand::create(IServer& server) diff --git a/srcs/core/Server.cpp b/srcs/core/Server.cpp index d0fbc6f..ce974b7 100644 --- a/srcs/core/Server.cpp +++ b/srcs/core/Server.cpp @@ -346,6 +346,14 @@ bool Server::requiresPassword() const return !m_cfg.getPassword().empty(); } +void Server::markForDisconnect(int fd) +{ + m_pendingDisconnects.insert(fd); + IClient* client = getClient(fd); + if (client && !client->getBuffer().getWriteBuffer().empty()) + m_sm->modifySocket(fd, EPOLLIN | EPOLLOUT); +} + static int createListeningSocket(int port) // fd that listens to all interfaces trying to bind { std::stringstream ss; diff --git a/tests/mocks/Server.cpp b/tests/mocks/Server.cpp index efb1a2d..e99a5c4 100644 --- a/tests/mocks/Server.cpp +++ b/tests/mocks/Server.cpp @@ -94,4 +94,9 @@ size_t Server::getChannelCount() const std::string Server::getServerName() const { return "mock_server.serv"; +} + +void Server::markForDisconnect(int fd) +{ + MOCK_LOG("Server marking fd " << fd << " for disconnect"); } \ No newline at end of file diff --git a/tests/mocks/Server.hpp b/tests/mocks/Server.hpp index d6aeadf..b656463 100644 --- a/tests/mocks/Server.hpp +++ b/tests/mocks/Server.hpp @@ -29,4 +29,5 @@ class Server : public IServer void deleteChannelIfEmpty(IChannel* channel); size_t getChannelCount() const; std::string getServerName() const; + void markForDisconnect(int fd); }; From dbeb083c8c0b34e889509df9c2a865c9f55032f2 Mon Sep 17 00:00:00 2001 From: Yanis Andry Date: Sat, 31 Jan 2026 09:29:22 +0100 Subject: [PATCH 4/5] build: Makefile wouldn't be a proper Makefile without the success joke --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 424bc09..67c3a43 100644 --- a/Makefile +++ b/Makefile @@ -110,7 +110,7 @@ $(NAME): $(OBJS) @if [ "$(DEBUG)" = "1" ]; then \ echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) compiled in $(call get_color,Red)DEBUG$(call get_color,Off) mode!"; \ else \ - echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) has been compiled!"; \ + echo "$(call get_color,Off)[$(ANNOUNCER_NAME)] $(call get_color,Purple)$(NAME)$(call get_color,Off) has been compiled! Netsplits are now a philosophical concept and PING timeouts build character :) (discord remains the disaster recovery plan)"; \ fi $(BONUS_MARKER): $(OBJS_BONUS) From d15c48301a89e619d5c499b43af49a483bc357de Mon Sep 17 00:00:00 2001 From: Yanis Andry Date: Sat, 31 Jan 2026 10:08:11 +0100 Subject: [PATCH 5/5] refactor: don't throw exceptions on EPOLL_CTL_DEL failures --- srcs/core/Server.cpp | 9 ++------- srcs/network/PollSocketManager.cpp | 13 +++++++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/srcs/core/Server.cpp b/srcs/core/Server.cpp index ce974b7..70577aa 100644 --- a/srcs/core/Server.cpp +++ b/srcs/core/Server.cpp @@ -194,13 +194,8 @@ static void setNonBlocking(int fd) void Server::disconnectClient(int fd) { m_pendingDisconnects.erase(fd); - try - { - m_sm->removeSocket(fd); - } - catch (...) - { // catch any exception "..." - } + + m_sm->removeSocket(fd); close(fd); std::map< int, IClient* >::iterator it = m_clients.find(fd); if (it != m_clients.end()) diff --git a/srcs/network/PollSocketManager.cpp b/srcs/network/PollSocketManager.cpp index e0af41e..35b87b0 100644 --- a/srcs/network/PollSocketManager.cpp +++ b/srcs/network/PollSocketManager.cpp @@ -1,5 +1,7 @@ #include "PollSocketManager.hpp" +#include "Logger.hpp" #include +#include const std::vector< epoll_event >& PollSocketManager::getEvents() const { @@ -11,7 +13,7 @@ int PollSocketManager::wait(int timeout_ms) int n = epoll_wait(m_epollFd, &m_events[0], (int)m_events.size(), timeout_ms); if (n == -1 && errno != EINTR) - throw std::runtime_error("epoll_wait failed\n"); + throw std::runtime_error("epoll_wait failed"); if (n == (int)m_events.size()) m_events.resize(m_events.size() * 2); @@ -29,13 +31,16 @@ void PollSocketManager::modifySocket(int fd, int events) evt.data.fd = fd; if (epoll_ctl(m_epollFd, EPOLL_CTL_MOD, fd, &evt) == -1) - throw std::runtime_error("Modification of socket failed\n"); + throw std::runtime_error("Modification of socket failed"); } void PollSocketManager::removeSocket(int fd) { + // if this fail we most likely don't care as it isn't critical per se + // basically it's caused by the fd already being removed from epoll so that's fine + // we can still log it though :) if (epoll_ctl(m_epollFd, EPOLL_CTL_DEL, fd, 0) == -1) - throw std::runtime_error("Deletion of socket failed\n"); + LOG_WARNING << "Deletion of socket " << fd << " failed" << std::endl; } void PollSocketManager::addSocket(int fd, int events) @@ -62,4 +67,4 @@ PollSocketManager::~PollSocketManager() { if (m_epollFd != -1) close(m_epollFd); -} \ No newline at end of file +}