From b786e79287c1b4aa3f8e8c839314efcb0e5acecf Mon Sep 17 00:00:00 2001 From: lohhiiccc <96543753+lohhiiccc@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:55:11 +0100 Subject: [PATCH] feat: add version generation and CLI parsing Add auto-generated version header and implement full command-line argument parsing with getopt_long support. --- .gitignore | 1 + Makefile | 28 ++++++++++++++- configure | 2 +- includes/cli.h | 24 +++++++++++++ src/cli/handlers/handler_map.c | 66 +++++++++++++++++++++++----------- src/cli/parse.c | 60 +++++++++++++++++++++++++++++-- src/main.c | 26 +++++++++++++- 7 files changed, 182 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 2244ef0..7105e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ build/ build.mk .cache/ compile_commands.json +includes/version_gen.h diff --git a/Makefile b/Makefile index 196a16a..1d31322 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,9 @@ endif NAME = ft_ping TEST_BIN = ft_ping.test +VERSION := 0.0.1 +BUILD_DATE := $(shell date -u '+%Y-%m-%d %H:%M:%S UTC') +GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "nogit") .DEFAULT_GOAL := all MAKEFLAGS += --no-print-directory @@ -35,8 +38,30 @@ DEPS = $(OBJS:.o=.d) # Build Rules +VERSION_HEADER = includes/version_gen.h + +$(VERSION_HEADER): Makefile + @echo "/* Auto-generated - DO NOT EDIT */" > $@ + @echo "#ifndef PING_VERSION_GEN_H" >> $@ + @echo "#define PING_VERSION_GEN_H" >> $@ + @echo "#define PING_VERSION \"$(VERSION)\"" >> $@ + @echo "#define PING_BUILD_DATE \"$(BUILD_DATE)\"" >> $@ + @echo "#define PING_GIT_COMMIT \"$(GIT_COMMIT)\"" >> $@ + @echo "" >> $@ + @echo "typedef struct s_prog_name" >> $@ + @echo "{" >> $@ + @echo " char *alloc;" >> $@ + @echo " const char *name;" >> $@ + @echo "} t_prog_name;" >> $@ + @echo "" >> $@ + @echo "extern t_prog_name g_prog_name;" >> $@ + @echo "" >> $@ + @echo "#endif" >> $@ + @echo "[OK] Version header generated" + + .PHONY: all -all: $(LIBICMP_DEP) $(NAME) +all: $(LIBICMP_DEP) $(VERSION_HEADER) $(NAME) # Build local libicmp if needed ifeq ($(BUILD_LOCAL_LIBICMP),yes) @@ -125,6 +150,7 @@ endif .PHONY: clean clean: @echo "[CLEAN] Cleaning object files..." + $(RM) $(VERSION_HEADER) $(RM) -r $(OBJ_DIR) ifeq ($(BUILD_LOCAL_LIBICMP),yes) @echo "[CLEAN] Cleaning libicmp..." diff --git a/configure b/configure index 9153168..36e129e 100755 --- a/configure +++ b/configure @@ -486,7 +486,7 @@ generate_build_mk() { # Prepare compiler flags local cflags="-Wall -Wextra -Werror -pipe" - local cppflags="-std=c99 -I includes" + local cppflags="-std=c99 -I includes -D_GNU_SOURCE" local ldflags="" local debug_flags="" local sanitizer_flags="" diff --git a/includes/cli.h b/includes/cli.h index 73df681..62c4396 100644 --- a/includes/cli.h +++ b/includes/cli.h @@ -5,6 +5,30 @@ typedef int (*t_option_handler)(const char *arg, t_ping_config *config); +typedef enum e_option_arg_type +{ + OPT_ARG_NONE = 0, + OPT_ARG_INT, + OPT_ARG_UINT, + OPT_ARG_SECONDS, + OPT_ARG_BYTES, + OPT_ARG_TTL, + OPT_ARG_STRING +} t_option_arg_type; + +typedef struct s_option_descriptor +{ + int short_opt; + const char *long_opt; + int has_arg; + t_option_handler handler; + t_option_arg_type arg_type; + const char *description; +} t_option_descriptor; + +extern const t_option_descriptor g_options[]; +extern const size_t g_options_len; + int cli_handle_count(const char *arg, t_ping_config *config); int cli_handle_flood(const char *arg, t_ping_config *config); int cli_handle_help(const char *arg, t_ping_config *config); diff --git a/src/cli/handlers/handler_map.c b/src/cli/handlers/handler_map.c index 1d7ab09..e2911fd 100644 --- a/src/cli/handlers/handler_map.c +++ b/src/cli/handlers/handler_map.c @@ -2,25 +2,51 @@ #include "cli.h" #include -typedef struct s_option_descriptor -{ - int short_opt; - const char *long_opt; - int has_arg; - t_option_handler handler; - const char *description; -} t_option_descriptor; - const t_option_descriptor g_options[] = { - {'h', "help", no_argument, cli_handle_help, "Display this help and exit"}, - {'V', "version", no_argument, cli_handle_version, "Display version information and exit"}, - {'v', "verbose", no_argument, cli_handle_verbose, "Verbose output"}, - {'q', "quiet", no_argument, cli_handle_quiet, "Quiet mode (only show summary)"}, - {'c', "count", required_argument, cli_handle_count, "Stop after sending N packets"}, - {'i', "interval", required_argument, cli_handle_interval, "Wait N seconds between packets"}, - {'t', "ttl", required_argument, cli_handle_ttl, "Set Time To Live"}, - {'s', "size", required_argument, cli_handle_size, "Packet size in bytes"}, - {'W', "timeout", required_argument, cli_handle_timeout, "Timeout for replies in seconds"}, - {'f', "flood", no_argument, cli_handle_flood, "Flood mode"}, - {0, NULL, 0, NULL, NULL} + { + 'h', "help", no_argument, cli_handle_help, OPT_ARG_NONE, + "Display this help and exit" + }, + { + 'V', "version", no_argument, cli_handle_version, OPT_ARG_NONE, + "Display version information and exit" + }, + { + 'v', "verbose", no_argument, cli_handle_verbose, OPT_ARG_NONE, + "Verbose output" + }, + { + 'q', "quiet", no_argument, cli_handle_quiet, OPT_ARG_NONE, + "Quiet mode (only show summary)" + }, + { + 'c', "count", required_argument, cli_handle_count, OPT_ARG_UINT, + "Stop after sending N packets" + }, + { + 'i', "interval", required_argument, cli_handle_interval, OPT_ARG_SECONDS, + "Wait N seconds between packets" + }, + { + 't', "ttl", required_argument, cli_handle_ttl, OPT_ARG_TTL, + "Set Time To Live" + }, + { + 's', "size", required_argument, cli_handle_size, OPT_ARG_BYTES, + "Packet size in bytes" + }, + { + 'W', "timeout", required_argument, cli_handle_timeout, OPT_ARG_SECONDS, + "Timeout for replies in seconds" + }, + { + 'f', "flood", no_argument, cli_handle_flood, OPT_ARG_NONE, + "Flood mode" + }, + { + 0, NULL, 0, NULL, OPT_ARG_NONE, + NULL + } }; + +const size_t g_options_len = ((sizeof(g_options) / sizeof(*g_options)) - 1); diff --git a/src/cli/parse.c b/src/cli/parse.c index 18438be..c098115 100644 --- a/src/cli/parse.c +++ b/src/cli/parse.c @@ -1,18 +1,51 @@ +#include #include #include +#include "cli.h" #include "ft_ping.h" #include "ft_ping_const.h" /* Forward declatation */ static void init_config(t_ping_config *config); +static void build_long_options(struct option *long_opts, size_t nb_opts, + const t_option_descriptor *src); +static const t_option_descriptor *find_option_handler(int opt); +static int hanlde_one_option(int opt, char *optarg, t_ping_config *config); /* ------------------- */ int cli_parse_arguments(int argc, char **argv, t_ping_config *config) { - (void)argc; - (void)argv; + struct option long_opts[g_options_len + 1]; + const char *opt_str = "hVvq:c:i:t:s:W:f"; + int opt; + int res; + init_config(config); + build_long_options(long_opts, g_options_len, g_options); + while (-1 != (opt = getopt_long(argc, argv, opt_str, long_opts, NULL))) + { + res = hanlde_one_option(opt, optarg, config); + if (0 != res) + return res; + } + return 0; +} + +static int +hanlde_one_option(int opt, char *optarg, t_ping_config *config) +{ + const t_option_descriptor *desc = find_option_handler(opt); + int res; + + if ('?' == opt) + return 1; + if (NULL != desc) + { + res = desc->handler(optarg, config); + if (0 != res) + return res; + } return 0; } @@ -27,3 +60,26 @@ init_config(t_ping_config *config) config->timeout = DEFAULT_TIMEOUT; } +static void +build_long_options(struct option *long_opts, size_t nb_opts, + const t_option_descriptor *src) +{ + for (size_t i = 0; i < nb_opts; ++i) { + long_opts[i].name = src[i].long_opt; + long_opts[i].has_arg = src[i].has_arg; + long_opts[i].flag = NULL; + long_opts[i].val = src[i].short_opt; + } + long_opts[nb_opts] = (struct option){0}; +} + +static const t_option_descriptor +*find_option_handler(int opt) +{ + for (size_t i = 0; i < g_options_len; ++i) + { + if (g_options[i].short_opt == opt) + return &g_options[i]; + } + return NULL; +} diff --git a/src/main.c b/src/main.c index 4483461..49f51de 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,15 @@ #include +#include #include +#include + #include "ft_ping.h" +#include "version_gen.h" + +/* Forward declarations */ +static int init_prog_name(char *name); +t_prog_name g_prog_name = {NULL, NULL}; +/* -------------------- */ int main(int argc, char **argv) @@ -8,11 +17,26 @@ main(int argc, char **argv) t_ping_config config; int ret; + if (0 != init_prog_name(argv[0])) + return EXIT_FAILURE; ret = cli_parse_arguments(argc, argv, &config); if (ret != 0) - return (ret < 0) ? EXIT_FAILURE : EXIT_SUCCESS; + return (ret > 0) ? EXIT_FAILURE : EXIT_SUCCESS; printf("PING: Not yet implemented\n"); + free(g_prog_name.alloc); return EXIT_SUCCESS; } + +static int +init_prog_name(char *name) +{ + char *path_dup = strdup(name); + if (NULL == path_dup) + return 1; + + g_prog_name.alloc = path_dup; + g_prog_name.name = basename(path_dup); + return 0; +}