/* * libslack - https://libslack.org * * Copyright (C) 1999-2004, 2010, 2020-2023 raf * * 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 2 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 . * * 20230824 raf */ /* =head1 NAME I - program framework module =head1 SYNOPSIS #include #include typedef struct option option; typedef struct Option Option; typedef struct Options Options; typedef void opt_action_int_t(int arg); typedef void opt_action_optional_int_t(int *arg); typedef void opt_action_string_t(const char *arg); typedef void opt_action_optional_string_t(const char *arg); typedef void opt_action_none_t(void); typedef void func_t(void); enum OptionArgument { OPT_NONE, OPT_INTEGER, OPT_STRING }; enum OptionAction { OPT_NOTHING, OPT_VARIABLE, OPT_FUNCTION }; typedef enum OptionArgument OptionArgument; typedef enum OptionAction OptionAction; struct Option { const char *name; char short_name; const char *argname; const char *desc; int has_arg; OptionArgument arg_type; OptionAction action; void *object; func_t *function; }; struct Options { Options *parent; Option *options; }; void prog_init(void); const char *prog_set_name(const char *name); Options *prog_set_options(Options *options); const char *prog_set_syntax(const char *syntax); const char *prog_set_desc(const char *desc); const char *prog_set_version(const char *version); const char *prog_set_date(const char *date); const char *prog_set_author(const char *author); const char *prog_set_contact(const char *contact); const char *prog_set_vendor(const char *vendor); const char *prog_set_url(const char *url); const char *prog_set_legal(const char *legal); Msg *prog_set_out(Msg *out); Msg *prog_set_err(Msg *err); Msg *prog_set_dbg(Msg *dbg); Msg *prog_set_alert(Msg *alert); ssize_t prog_set_debug_level(size_t debug_level); ssize_t prog_set_verbosity_level(size_t verbosity_level); int prog_set_locker(Locker *locker); const char *prog_name(void); const Options *prog_options(void); const char *prog_syntax(void); const char *prog_desc(void); const char *prog_version(void); const char *prog_date(void); const char *prog_author(void); const char *prog_contact(void); const char *prog_vendor(void); const char *prog_url(void); const char *prog_legal(void); Msg *prog_out(void); Msg *prog_err(void); Msg *prog_dbg(void); Msg *prog_alert(void); size_t prog_debug_level(void); size_t prog_verbosity_level(void); int prog_out_fd(int fd); int prog_out_stdout(void); int prog_out_file(const char *path); int prog_out_syslog(const char *ident, int option, int facility, int priority); int prog_out_push_filter(msg_filter_t *filter); int prog_out_none(void); int prog_err_fd(int fd); int prog_err_stderr(void); int prog_err_file(const char *path); int prog_err_syslog(const char *ident, int option, int facility, int priority); int prog_err_push_filter(msg_filter_t *filter); int prog_err_none(void); int prog_dbg_fd(int fd); int prog_dbg_stdout(void); int prog_dbg_stderr(void); int prog_dbg_file(const char *path); int prog_dbg_syslog(const char *id, int option, int facility, int priority); int prog_dbg_push_filter(msg_filter_t *filter); int prog_dbg_none(void); int prog_alert_fd(int fd); int prog_alert_stdout(void); int prog_alert_stderr(void); int prog_alert_file(const char *path); int prog_alert_syslog(const char *id, int option, int facility, int priority); int prog_alert_push_filter(msg_filter_t *filter); int prog_alert_none(void); int prog_opt_process(int ac, char **av); void prog_usage_msg(const char *format, ...); void prog_help_msg(void); void prog_version_msg(void); const char *prog_basename(const char *path); extern Options prog_options_table[1]; int opt_process(int argc, char **argv, Options *options, char *msgbuf, size_t bufsize); char *opt_usage(char *buf, size_t size, Options *options); =head1 DESCRIPTION This module provides administrative services for arbitrary programs. The services include program identification; flexible, complete command line option processing; help, usage and version messages; flexible debug, verbose, error and normal messaging (simple call syntax with arbitrary message destinations including multiplexing). This module exposes an alternate interface to I. It defines a way to specify command line option syntax, semantics and descriptions in multiple, discrete chunks. The I functions require that the client specify the syntax and partial semantics for all options in the same place (if it is to be done statically). This can be annoying when library modules require their own command line options. This module allows various parts of a program to (statically) specify their own command line options independently, and link them together via C pointers. Option syntax is specified in much the same way as for I. Option semantics are specified by an action (C, C or C), an argument type (C, C or C), and either an object (C, C) or function (C, C or C). The I and I functions are used by the I functions and needn't be used directly. Instead, use I to execute options and I and I to construct usage and help messages directly from the supplied option data. They are exposed in case you don't want to use any other part of this module. =over 4 =cut */ #ifndef _BSD_SOURCE #define _BSD_SOURCE /* For snprintf() on OpenBSD-4.7 */ #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE /* New name for _BSD_SOURCE */ #endif #include "config.h" #include "std.h" #include "msg.h" #include "err.h" #include "mem.h" #include "prog.h" #ifndef HAVE_SNPRINTF #include "snprintf.h" #endif typedef struct Prog Prog; struct Prog { const char *name; Options *options; const char *syntax; const char *desc; const char *version; const char *date; const char *author; const char *contact; const char *vendor; const char *url; const char *legal; Msg *out; Msg *err; Msg *dbg; Msg *log; size_t debug_level; size_t verbosity_level; Locker *locker; }; #ifndef TEST static Prog g = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL }; /* =item C Initialises the message, error, debug, and alert destinations to C, C, C, and C, respectively. These are all C by default so this function must be called before any messages are emitted. =cut */ void prog_init(void) { prog_out_stdout(); prog_err_stderr(); prog_dbg_stderr(); prog_alert_stderr(); } /* =item C Sets the program's name to C. This is used when composing usage, help, version, and error messages. On success, returns C. On error, returns C with C set appropriately. =cut */ #define RDLOCK(ret) { int rc; if ((rc = locker_rdlock(g.locker))) { set_errno(rc); return (ret); } } #define WRLOCK(ret) { int rc; if ((rc = locker_wrlock(g.locker))) { set_errno(rc); return (ret); } } #define UNLOCK(ret) { int rc; if ((rc = locker_unlock(g.locker))) { set_errno(rc); return (ret); } } #define PROG_SET_STR_AND_RETURN(name, value) \ WRLOCK(NULL) \ name = value; \ UNLOCK(NULL) \ return value #define PROG_SET_MSG_AND_RETURN(name, value) \ WRLOCK(NULL) \ if (name && name != value) \ msg_release(name); \ name = value; \ UNLOCK(NULL) \ return value #define PROG_POP_MSG(name, lvalue) \ WRLOCK(-1) \ lvalue = name; \ name = NULL; \ UNLOCK(-1) \ #define PROG_SET_INT_AND_RETURN_PREVIOUS(name, value) \ size_t prev; \ WRLOCK(-1) \ prev = name; \ name = value; \ UNLOCK(-1) \ return prev #define PROG_GET_PTR_AND_RETURN(name) \ void *value; \ RDLOCK(NULL) \ value = (void *)name; \ UNLOCK(NULL) \ return value #define PROG_GET_INT_AND_RETURN(name) \ int value; \ RDLOCK(0) \ value = name; \ UNLOCK(0) \ return value const char *prog_set_name(const char *name) { PROG_SET_STR_AND_RETURN(g.name, name); } /* =item C Sets the program's options to C. This is used when processing the command line options with I. On success, returns C. On error, returns C with C set appropriately. =cut */ Options *prog_set_options(Options *options) { PROG_SET_STR_AND_RETURN(g.options, options); } /* =item C Sets the program's command line syntax summary to C. This is used when composing usage and help messages. It must contain a one line summary of the command line arguments, excluding the program name (e.g. C<"[options] arg...">). On success, returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_syntax(const char *syntax) { PROG_SET_STR_AND_RETURN(g.syntax, syntax); } /* =item C Sets the program's description to C. This is used when composing help messages. On success, returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_desc(const char *desc) { PROG_SET_STR_AND_RETURN(g.desc, desc); } /* =item C Sets the program's version string to C. This is used when composing help and version messages. On success, returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_version(const char *version) { PROG_SET_STR_AND_RETURN(g.version, version); } /* =item C Sets the program's release date to C. This is used when composing help messages. On success, returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_date(const char *date) { PROG_SET_STR_AND_RETURN(g.date, date); } /* =item C Sets the program's author to C. This is used when composing help messages. It must contain the (free format) name of the author. Returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_author(const char *author) { PROG_SET_STR_AND_RETURN(g.author, author); } /* =item C Sets the program's contact address to C. This is used when composing help messages. It must contain the email address to which bug reports should be sent. On success, returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_contact(const char *contact) { PROG_SET_STR_AND_RETURN(g.contact, contact); } /* =item C Sets the program's vendor to C. This is used when composing help messages. It must contain the (free format) name of the vendor. Returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_vendor(const char *vendor) { PROG_SET_STR_AND_RETURN(g.vendor, vendor); } /* =item C Sets the program's URL to C. This is used when composing help messages. It must contain the URL where the program can be downloaded. On success, returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_url(const char *url) { PROG_SET_STR_AND_RETURN(g.url, url); } /* =item C Sets the program's legal notice to C. This is used when composing help messages. It is assumed that the legal notice may contain multiple lines and so must contain its own newline characters. On success, returns C. On error, returns C with C set appropriately. =cut */ const char *prog_set_legal(const char *legal) { PROG_SET_STR_AND_RETURN(g.legal, legal); } /* =item C Sets the program's message destination to C. This is used by I and I which are, in turn, used to emit usage, version and help messages. The program message destination is set to standard output by I, but it can be anything. However, it is probably best to leave it as standard output at least until after command line option processing is complete. See I for details. On success, returns C. On error, returns C with C set appropriately. =cut */ Msg *prog_set_out(Msg *out) { PROG_SET_MSG_AND_RETURN(g.out, out); } /* =item C Sets the program's error message destination to C. This is used by I, I, I, I, I and I. The program error message destination is set to standard error by I, but it can be anything. See I for details. On success, returns C. On error, returns C with C set appropriately. =cut */ Msg *prog_set_err(Msg *err) { PROG_SET_MSG_AND_RETURN(g.err, err); } /* =item C Sets the program's debug message destination to C. This is set to standard error by I, but it can be set to anything. See I for details. On success, returns C. On error, returns C with C set appropriately. =cut */ Msg *prog_set_dbg(Msg *dbg) { PROG_SET_MSG_AND_RETURN(g.dbg, dbg); } /* =item C Sets the program's alert message destination to C. This is set to standard error by I but it can be set to anything. See I for details. On success, returns C. On error, returns C with C set appropriately. =cut */ Msg *prog_set_alert(Msg *alert) { PROG_SET_MSG_AND_RETURN(g.log, alert); } /* =item C Sets the program's debug level to C. This is used when determining whether or not to emit a debug message. The debug level comprises two parts, the I
and the I. The I occupies the low byte of C. The I
occupies the next three bytes. This enables debugging to be partitioned into sections, allowing users to turn on debugging at any level (from 0 up to 255) for particular sections of a program (at most 24). Debug messages with a section value whose bits overlap those of the program's current debug section and with a level that is less than or equal to the program's current debug level are emitted. As a convenience, if the program debug section is zero, debug messages with a sufficiently small level are emitted regardless of the message section. On success, returns the previous debug level. On error, returns C<-1> with C set appropriately. Example: #define LEXER_SECTION (1 << 8) #define PARSER_SECTION (2 << 8) #define INTERP_SECTION (4 << 8) prog_set_debug_level(LEXER_SECTION | PARSER_SECTION | 1); debug((LEXER_SECTION | 1, "lexer debugmsg")) // yes debug((LEXER_SECTION | 4, "lexer debugmsg")) // no (level too high) debug((PARSER_SECTION | 1, "parser debugmsg")) // yes debug((INTERP_SECTION | 1, "interp debugmsg")) // no (wrong section) debug((1, "global debug")) // no (no section to match) prog_set_debug_level(1); debug((LEXER_SECTION | 1, "lexer debugmsg")) // yes debug((LEXER_SECTION | 4, "lexer debugmsg")) // no (level too high) debug((PARSER_SECTION | 1, "parser debugmsg")) // yes debug((INTERP_SECTION | 1, "interp debugmsg")) // yes debug((1, "global debugmsg")) // yes debug((4, "global debugmsg")) // no (level too high) =cut */ ssize_t prog_set_debug_level(size_t debug_level) { PROG_SET_INT_AND_RETURN_PREVIOUS(g.debug_level, debug_level); } /* =item C Sets the program's verbosity level to C. This is used to determine whether or not to emit verbose messages. Verbose messages with a level that is less than or equal to the program's current verbosity level are emitted. On success, returns the previous verbosity level. On error, returns C<-1> with C set appropriately. =cut */ ssize_t prog_set_verbosity_level(size_t verbosity_level) { PROG_SET_INT_AND_RETURN_PREVIOUS(g.verbosity_level, verbosity_level); } /* =item C Sets the locker (multiple thread synchronisation strategy) for this module. This is only needed in multi-threaded programs. See I for details. On success, returns C<0>. On error, returns C<-1> with C set appropriately. =cut */ int prog_set_locker(Locker *locker) { if (g.locker) return -1; g.locker = locker; return 0; } /* =item C Returns the program's name as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_name(void) { PROG_GET_PTR_AND_RETURN(g.name); } /* =item C Returns the program's options as set by I. On error, returns C with C set appropriately. =cut */ const Options *prog_options(void) { PROG_GET_PTR_AND_RETURN(g.options); } /* =item C Returns the program's command line syntax summary as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_syntax(void) { PROG_GET_PTR_AND_RETURN(g.syntax); } /* =item C Returns the program's description as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_desc(void) { PROG_GET_PTR_AND_RETURN(g.desc); } /* =item C Returns the program's version string as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_version(void) { PROG_GET_PTR_AND_RETURN(g.version); } /* =item C Returns the program's release date as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_date(void) { PROG_GET_PTR_AND_RETURN(g.date); } /* =item C Returns the program's author as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_author(void) { PROG_GET_PTR_AND_RETURN(g.author); } /* =item C Returns the program's contact address as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_contact(void) { PROG_GET_PTR_AND_RETURN(g.contact); } /* =item C Returns the program's vendor as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_vendor(void) { PROG_GET_PTR_AND_RETURN(g.vendor); } /* =item C Returns the program's URL as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_url(void) { PROG_GET_PTR_AND_RETURN(g.url); } /* =item C Returns the program's legal notice as set by I. On error, returns C with C set appropriately. =cut */ const char *prog_legal(void) { PROG_GET_PTR_AND_RETURN(g.legal); } /* =item C Returns the program's message destination as set by I. On error, returns C with C set appropriately. =cut */ Msg *prog_out(void) { PROG_GET_PTR_AND_RETURN(g.out); } /* =item C Returns the program's error message destination as set by I. On error, returns C with C set appropriately. =cut */ Msg *prog_err(void) { PROG_GET_PTR_AND_RETURN(g.err); } /* =item C Returns the program's debug message destination as set by I. On error, returns C with C set appropriately. =cut */ Msg *prog_dbg(void) { PROG_GET_PTR_AND_RETURN(g.dbg); } /* =item C Returns the program's alert message destination as set by I. On error, returns C with C set appropriately. =cut */ Msg *prog_alert(void) { PROG_GET_PTR_AND_RETURN(g.log); } /* =item C Returns the program's debug level as set by I. On error, returns C<0> with C set appropriately. =cut */ size_t prog_debug_level(void) { PROG_GET_INT_AND_RETURN(g.debug_level); } /* =item C Returns the program's verbosity level as set by I. On error, returns C<0> with C set appropriately. =cut */ size_t prog_verbosity_level(void) { PROG_GET_INT_AND_RETURN(g.verbosity_level); } /* =item C Sets the program's normal message destination to be the file descriptor, C. On success, returns C<0>. On error, returns C<-1> with C set appropriately. =cut */ int prog_out_fd(int fd) { Msg *mesg; if (!(mesg = msg_create_fd_with_locker(g.locker, fd))) return -1; if (!prog_set_out(mesg)) { msg_release(mesg); return -1; } return 0; } /* =item C Sets the program's normal message destination to be standard output. On success, returns C<0>. On error, returns C<-1> with C set appropriately. =cut */ int prog_out_stdout(void) { return prog_out_fd(STDOUT_FILENO); } /* =item C Sets the program's normal message destination to be the file specified by C. On success, returns C<0>. On error, returns C<-1> with C set appropriately. =cut */ int prog_out_file(const char *path) { Msg *mesg; if (!(mesg = msg_create_file_with_locker(g.locker, path))) return -1; if (!prog_set_out(mesg)) { msg_release(mesg); return -1; } return 0; } /* =item C Sets the program's normal message destination to be I initialised with C, C