X-Git-Url: http://git.pk910.de/?p=ircu2.10.12-pk.git;a=blobdiff_plain;f=ircd%2Fs_stats.c;fp=ircd%2Fs_stats.c;h=7a600ce8f6e66d3636611936f3bb8753ed21900b;hp=0000000000000000000000000000000000000000;hb=0400a5a6479398d82526785c18c0df8bc8b92dce;hpb=d17e10da972ce5776c60b4c317267c6abe0e1ead diff --git a/ircd/s_stats.c b/ircd/s_stats.c new file mode 100644 index 0000000..7a600ce --- /dev/null +++ b/ircd/s_stats.c @@ -0,0 +1,713 @@ +/* + * IRC - Internet Relay Chat, ircd/s_stats.c + * Copyright (C) 2000 Joseph Bongaarts + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * 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 1, 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include "config.h" + +#include "class.h" +#include "client.h" +#include "gline.h" +#include "hash.h" +#include "ircd.h" +#include "ircd_chattr.h" +#include "ircd_events.h" +#include "ircd_features.h" +#include "ircd_crypt.h" +#include "ircd_log.h" +#include "ircd_reply.h" +#include "ircd_string.h" +#include "listener.h" +#include "list.h" +#include "match.h" +#include "motd.h" +#include "msg.h" +#include "msgq.h" +#include "numeric.h" +#include "numnicks.h" +#include "querycmds.h" +#include "res.h" +#include "s_auth.h" +#include "s_bsd.h" +#include "s_conf.h" +#include "s_debug.h" +#include "s_misc.h" +#include "s_serv.h" +#include "s_stats.h" +#include "s_user.h" +#include "send.h" +#include "struct.h" +#include "userload.h" + +#include +#include +#include +#include + +/** @file + * @brief Report configuration lines and other statistics from this + * server. + * @version $Id$ + * + * Note: The info is reported in the order the server uses + * it--not reversed as in ircd.conf! + */ + +/* The statsinfo array should only be used in this file, but just TRY + * telling the compiler that you want to forward declare a static + * array without specifying a length, and see how it responds. So we + * forward declare it "extern". + */ +extern struct StatDesc statsinfo[]; + +/** Report items from #GlobalConfList. + * Uses sd->sd_funcdata as a filter for ConfItem::status. + * @param[in] sptr Client requesting statistics. + * @param[in] sd Stats descriptor for request. + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_configured_links(struct Client *sptr, const struct StatDesc* sd, + char* param) +{ + static char null[] = ""; + struct ConfItem *tmp; + unsigned short int port; + int maximum; + char *host, *name, *username, *hub_limit; + + for (tmp = GlobalConfList; tmp; tmp = tmp->next) + { + if ((tmp->status & sd->sd_funcdata)) + { + host = BadPtr(tmp->host) ? null : tmp->host; + name = BadPtr(tmp->name) ? null : tmp->name; + username = BadPtr(tmp->username) ? null : tmp->username; + hub_limit = BadPtr(tmp->hub_limit) ? null : tmp->hub_limit; + maximum = tmp->maximum; + port = tmp->address.port; + + if (tmp->status & CONF_UWORLD) + send_reply(sptr, RPL_STATSULINE, host); + else if (tmp->status & CONF_SERVER) + send_reply(sptr, RPL_STATSCLINE, name, port, maximum, hub_limit, get_conf_class(tmp)); + else if (tmp->status & CONF_CLIENT) + send_reply(sptr, RPL_STATSILINE, + (tmp->username ? tmp->username : ""), (tmp->username ? "@" : ""), + (tmp->host ? tmp->host : "*"), maximum, + (name[0] == ':' ? "0" : ""), (tmp->name ? tmp->name : "*"), + port, get_conf_class(tmp)); + else if (tmp->status & CONF_OPERATOR) + { + int global = FlagHas(&tmp->privs_dirty, PRIV_PROPAGATE) + ? FlagHas(&tmp->privs, PRIV_PROPAGATE) + : FlagHas(&tmp->conn_class->privs, PRIV_PROPAGATE); + send_reply(sptr, RPL_STATSOLINE, global ? 'O' : 'o', + username, host, name, get_conf_class(tmp)); + } + } + } +} + +/** Report connection rules from conf_get_crule_list(). + * Uses sd->sd_funcdata as a filter for CRuleConf::type. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request. + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_crule_list(struct Client* to, const struct StatDesc *sd, + char *param) +{ + const struct CRuleConf* p = conf_get_crule_list(); + + for ( ; p; p = p->next) + { + if (p->type & sd->sd_funcdata) + send_reply(to, RPL_STATSDLINE, (p->type & CRULE_ALL ? 'D' : 'd'), p->hostmask, p->rule); + } +} + +/** Report active event engine name. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_engine(struct Client *to, const struct StatDesc *sd, char *param) +{ + send_reply(to, RPL_STATSENGINE, engine_name()); +} + +/** Report client access lists. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request. + * @param[in] param Filter for hostname or IP (NULL to show all). + */ +static void +stats_access(struct Client *to, const struct StatDesc *sd, char *param) +{ + struct ConfItem *aconf; + int wilds = 0; + int count = 1000; + + if (!param) + { + stats_configured_links(to, sd, param); + return; + } + + wilds = string_has_wildcards(param); + + for (aconf = GlobalConfList; aconf; aconf = aconf->next) + { + if (aconf->status != CONF_CLIENT) + continue; + if (wilds ? ((aconf->host && !mmatch(aconf->host, param)) + || (aconf->name && !mmatch(aconf->name, param))) + : ((aconf->host && !match(param, aconf->host)) + || (aconf->name && !match(param, aconf->name)))) + { + send_reply(to, RPL_STATSILINE, + (aconf->username ? aconf->username : ""), (aconf->username ? "@" : ""), + (aconf->host ? aconf->host : "*"), aconf->maximum, + (aconf->name && aconf->name[0] == ':' ? "0":""), + aconf->name ? aconf->name : "*", + aconf->address.port, get_conf_class(aconf)); + if (--count == 0) + break; + } + } +} + + +/** Report DenyConf entries. + * @param[in] to Client requesting list. + */ +static void +report_deny_list(struct Client* to) +{ + const struct DenyConf* p = conf_get_deny_list(); + for ( ; p; p = p->next) + send_reply(to, RPL_STATSKLINE, p->bits > 0 ? 'k' : 'K', + p->usermask ? p->usermask : "*", + p->hostmask ? p->hostmask : "*", + p->message ? p->message : "(none)", + p->realmask ? p->realmask : "*"); +} + +/** Report K/k-lines to a user. + * @param[in] sptr Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] mask Filter for hostmasks to show. + */ +static void +stats_klines(struct Client *sptr, const struct StatDesc *sd, char *mask) +{ + int wilds = 0; + int count = 3; + int limit_query = 0; + char *user = 0; + char *host; + const struct DenyConf* conf; + + if (!IsAnOper(sptr)) + limit_query = 1; + + if (!mask) + { + if (limit_query) + need_more_params(sptr, "STATS K"); + else + report_deny_list(sptr); + return; + } + + if (!limit_query) + { + wilds = string_has_wildcards(mask); + count = 1000; + } + if ((host = strchr(mask, '@'))) + { + user = mask; + *host++ = '\0'; + } + else + host = mask; + + for (conf = conf_get_deny_list(); conf; conf = conf->next) + { + /* Skip this block if the user is searching for a user-matching + * mask but the current Kill doesn't have a usermask, or if user + * is searching for a host-matching mask but the Kill has no + * hostmask, or if the user mask is specified and doesn't match, + * or if the host mask is specified and doesn't match. + */ + if ((user && !conf->usermask) + || (host && !conf->hostmask) + || (user && conf->usermask + && (wilds + ? mmatch(user, conf->usermask) + : match(conf->usermask, user))) + || (host && conf->hostmask + && (wilds + ? mmatch(host, conf->hostmask) + : match(conf->hostmask, host)))) + continue; + send_reply(sptr, RPL_STATSKLINE, conf->bits > 0 ? 'k' : 'K', + conf->usermask ? conf->usermask : "*", + conf->hostmask ? conf->hostmask : "*", + conf->message ? conf->message : "(none)", + conf->realmask ? conf->realmask : "*"); + if (--count == 0) + return; + } +} + +/** Report on servers and/or clients connected to the network. + * @param[in] sptr Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] name Filter for client names to show. + */ +static void +stats_links(struct Client* sptr, const struct StatDesc* sd, char* name) +{ + struct Client *acptr; + int i; + int wilds = 0; + + if (name) + wilds = string_has_wildcards(name); + + /* + * Send info about connections which match, or all if the + * mask matches me.name. Only restrictions are on those who + * are invisible not being visible to 'foreigners' who use + * a wild card based search to list it. + */ + send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, "Connection SendQ " + "SendM SendKBytes RcveM RcveKBytes :Open since"); + for (i = 0; i <= HighestFd; i++) + { + if (!(acptr = LocalClientArray[i])) + continue; + /* Don't return clients when this is a request for `all' */ + if (!name && IsUser(acptr)) + continue; + /* Don't show invisible people to non opers unless they know the nick */ + if (IsInvisible(acptr) && (!name || wilds) && !IsAnOper(acptr) && + (acptr != sptr)) + continue; + /* Only show the ones that match the given mask - if any */ + if (name && wilds && match(name, cli_name(acptr))) + continue; + /* Skip all that do not match the specific query */ + if (!(!name || wilds) && 0 != ircd_strcmp(name, cli_name(acptr))) + continue; + send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, + "%s %u %u %Lu %u %Lu :%Tu", + (*(cli_name(acptr))) ? cli_name(acptr) : "", + (int)MsgQLength(&(cli_sendQ(acptr))), (int)cli_sendM(acptr), + (cli_sendB(acptr) >> 10), (int)cli_receiveM(acptr), + (cli_receiveB(acptr) >> 10), CurrentTime - cli_firsttime(acptr)); + } +} + +/** Report on loaded modules. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_modules(struct Client* to, const struct StatDesc* sd, char* param) +{ +crypt_mechs_t* mechs; + + send_reply(to, SND_EXPLICIT | RPL_STATSLLINE, + "Module Description Entry Point"); + + /* atm the only "modules" we have are the crypto mechanisms, + eventualy they'll be part of a global dl module list, for now + i'll just output data about them -- hikari */ + + if(crypt_mechs_root == NULL) + return; + + mechs = crypt_mechs_root->next; + + for(;;) + { + if(mechs == NULL) + return; + + send_reply(to, SND_EXPLICIT | RPL_STATSLLINE, + "%s %s 0x%X", + mechs->mech->shortname, mechs->mech->description, + mechs->mech->crypt_function); + + mechs = mechs->next; + } + +} + +/** Report how many times each command has been used. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_commands(struct Client* to, const struct StatDesc* sd, char* param) +{ + struct Message *mptr; + + for (mptr = msgtab; mptr->cmd; mptr++) + if (mptr->count) + send_reply(to, RPL_STATSCOMMANDS, mptr->cmd, mptr->count, mptr->bytes); +} + +/** List channel quarantines. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] param Filter for quarantined channel names. + */ +static void +stats_quarantine(struct Client* to, const struct StatDesc* sd, char* param) +{ + struct qline *qline; + + for (qline = GlobalQuarantineList; qline; qline = qline->next) + { + if (param && match(param, qline->chname)) /* narrow search */ + continue; + send_reply(to, RPL_STATSQLINE, qline->chname, qline->reason); + } +} + +/** List service pseudo-command mappings. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_mapping(struct Client *to, const struct StatDesc* sd, char* param) +{ + struct s_map *map; + + send_reply(to, RPL_STATSRLINE, "Command", "Name", "Prepend", "Target"); + for (map = GlobalServiceMapList; map; map = map->next) { + struct nick_host *nh; + for (nh = map->services; nh; nh = nh->next) { + send_reply(to, RPL_STATSRLINE, map->command, map->name, + (map->prepend ? map->prepend : "*"), nh->nick); + } + } +} + +/** Report server uptime and maximum connection/client counts. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_uptime(struct Client* to, const struct StatDesc* sd, char* param) +{ + time_t nowr; + + nowr = CurrentTime - cli_since(&me); + send_reply(to, RPL_STATSUPTIME, nowr / 86400, (nowr / 3600) % 24, + (nowr / 60) % 60, nowr % 60); + send_reply(to, RPL_STATSCONN, max_connection_count, max_client_count); +} + +/** Verbosely report on servers connected to the network. + * If sd->sd_funcdata != 0, then display in a more human-friendly format. + * @param[in] sptr Client requesting statistics. + * @param[in] sd Stats descriptor for request. + * @param[in] param Filter for server names to display. + */ +static void +stats_servers_verbose(struct Client* sptr, const struct StatDesc* sd, + char* param) +{ + struct Client *acptr; + const char *fmt; + + /* + * lowercase 'v' is for human-readable, + * uppercase 'V' is for machine-readable + */ + if (sd->sd_funcdata) { + send_reply(sptr, SND_EXPLICIT | RPL_STATSVERBOSE, + "%-20s %-20s Flags Hops Numeric Lag RTT Up Down " + "Clients/Max Proto %-10s :Info", "Servername", "Uplink", + "LinkTS"); + fmt = "%-20s %-20s %c%c%c%c%c %4i %s %-4i %5i %4i %4i %4i %5i %5i P%-2i %Tu :%s"; + } else { + fmt = "%s %s %c%c%c%c%c %i %s %i %i %i %i %i %i %i P%i %Tu :%s"; + } + + for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) + { + if (!IsServer(acptr) && !IsMe(acptr)) + continue; + /* narrow search */ + if (param && match(param, cli_name(acptr))) + continue; + send_reply(sptr, SND_EXPLICIT | RPL_STATSVERBOSE, fmt, + cli_name(acptr), + cli_name(cli_serv(acptr)->up), + IsBurst(acptr) ? 'B' : '-', + IsBurstAck(acptr) ? 'A' : '-', + IsHub(acptr) ? 'H' : '-', + IsService(acptr) ? 'S' : '-', + IsIPv6(acptr) ? '6' : '-', + cli_hopcount(acptr), + NumServ(acptr), + base64toint(cli_yxx(acptr)), + cli_serv(acptr)->lag, + cli_serv(acptr)->asll_rtt, + cli_serv(acptr)->asll_to, + cli_serv(acptr)->asll_from, + (acptr == &me ? UserStats.local_clients : cli_serv(acptr)->clients), + cli_serv(acptr)->nn_mask, + cli_serv(acptr)->prot, + cli_serv(acptr)->timestamp, + cli_info(acptr)); + } +} + +/** Display objects allocated (and total memory used by them) for + * several types of structures. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request (ignored). + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_meminfo(struct Client* to, const struct StatDesc* sd, char* param) +{ + extern void bans_send_meminfo(struct Client *cptr); + + class_send_meminfo(to); + bans_send_meminfo(to); + send_listinfo(to, 0); +} + +/** Send a list of available statistics. + * @param[in] to Client requesting statistics. + * @param[in] sd Stats descriptor for request. + * @param[in] param Extra parameter from user (ignored). + */ +static void +stats_help(struct Client* to, const struct StatDesc* sd, char* param) +{ + struct StatDesc *asd; + + /* only if it's my user */ + if (MyUser(to)) + for (asd = statsinfo; asd->sd_name; asd++) + if (asd != sd) /* don't send the help for us */ + sendcmdto_one(&me, CMD_NOTICE, to, "%C :%c (%s) - %s", to, asd->sd_c, + asd->sd_name, asd->sd_desc); +} + +/** Contains information about all statistics. */ +struct StatDesc statsinfo[] = { + { 'a', "nameservers", STAT_FLAG_OPERFEAT|STAT_FLAG_LOCONLY, FEAT_HIS_STATS_a, + report_dns_servers, 0, + "DNS servers." }, + { 'c', "connect", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_c, + stats_configured_links, CONF_SERVER, + "Remote server connection lines." }, + { 'd', "maskrules", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_d, + stats_crule_list, CRULE_MASK, + "Dynamic routing configuration." }, + { 'D', "crules", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_d, + stats_crule_list, CRULE_ALL, + "Dynamic routing configuration." }, + { 'e', "engine", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_e, + stats_engine, 0, + "Report server event loop engine." }, + { 'f', "features", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_f, + feature_report, 0, + "Feature settings." }, + { 'F', "featuresall", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_f, + feature_report, 1, + "All feature settings, including defaulted values." }, + { 'g', "glines", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_g, + gline_stats, 0, + "Global bans (G-lines)." }, + { 'i', "access", (STAT_FLAG_OPERFEAT | STAT_FLAG_VARPARAM), FEAT_HIS_STATS_i, + stats_access, CONF_CLIENT, + "Connection authorization lines." }, + { 'j', "histogram", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_j, + msgq_histogram, 0, + "Message length histogram." }, + { 'J', "jupes", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_J, + stats_nickjupes, 0, + "Nickname jupes." }, + { 'k', "klines", (STAT_FLAG_OPERFEAT | STAT_FLAG_VARPARAM), FEAT_HIS_STATS_k, + stats_klines, 0, + "Local bans (K-Lines)." }, + { 'l', "links", (STAT_FLAG_OPERFEAT | STAT_FLAG_VARPARAM | STAT_FLAG_CASESENS), + FEAT_HIS_STATS_l, + stats_links, 0, + "Current connections information." }, + { 'L', "modules", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), + FEAT_HIS_STATS_L, + stats_modules, 0, + "Dynamically loaded modules." }, + { 'm', "commands", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_m, + stats_commands, 0, + "Message usage information." }, + { 'o', "operators", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_o, + stats_configured_links, CONF_OPERATOR, + "Operator information." }, + { 'p', "ports", (STAT_FLAG_OPERFEAT | STAT_FLAG_VARPARAM), FEAT_HIS_STATS_p, + show_ports, 0, + "Listening ports." }, + { 'q', "quarantines", (STAT_FLAG_OPERONLY | STAT_FLAG_VARPARAM), FEAT_HIS_STATS_q, + stats_quarantine, 0, + "Quarantined channels list." }, + { 'R', "mappings", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_R, + stats_mapping, 0, + "Service mappings." }, +#ifdef DEBUGMODE + { 'r', "usage", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_r, + send_usage, 0, + "System resource usage (Debug only)." }, +#endif + { 'T', "motds", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_T, + motd_report, 0, + "Configured Message Of The Day files." }, + { 't', "locals", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_t, + tstats, 0, + "Local connection statistics (Total SND/RCV, etc)." }, + { 'U', "uworld", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_U, + stats_configured_links, CONF_UWORLD, + "Service server information." }, + { 'u', "uptime", (STAT_FLAG_OPERFEAT | STAT_FLAG_CASESENS), FEAT_HIS_STATS_u, + stats_uptime, 0, + "Current uptime & highest connection count." }, + { 'v', "vservers", (STAT_FLAG_OPERFEAT | STAT_FLAG_VARPARAM | STAT_FLAG_CASESENS), FEAT_HIS_STATS_v, + stats_servers_verbose, 1, + "Verbose server information." }, + { 'V', "vserversmach", (STAT_FLAG_OPERFEAT | STAT_FLAG_VARPARAM | STAT_FLAG_CASESENS), FEAT_HIS_STATS_v, + stats_servers_verbose, 0, + "Verbose server information." }, + { 'w', "userload", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_w, + calc_load, 0, + "Userload statistics." }, + { 'x', "memusage", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_x, + stats_meminfo, 0, + "List usage information." }, + { 'y', "classes", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_y, + report_classes, 0, + "Connection classes." }, + { 'z', "memory", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_z, + count_memory, 0, + "Memory/Structure allocation information." }, + { ' ', "iauth", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_IAUTH, + report_iauth_stats, 0, + "IAuth statistics." }, + { ' ', "iauthconf", STAT_FLAG_OPERFEAT, FEAT_HIS_STATS_IAUTH, + report_iauth_conf, 0, + "IAuth configuration." }, + { '*', "help", STAT_FLAG_CASESENS, FEAT_LAST_F, + stats_help, 0, + "Send help for stats." }, + { '\0', 0, FEAT_LAST_F, 0, 0, 0 } +}; + +/** Maps from characters to statistics descriptors. + * Statistics descriptors with no single-character alias are not included. + */ +static struct StatDesc *statsmap[256]; +/** Number of statistics descriptors. */ +static int statscount; + +/** Compare two StatDesc structures by long name (StatDesc::sd_name). + * @param[in] a_ Pointer to a StatDesc. + * @param[in] b_ Pointer to a StatDesc. + * @return Less than, equal to, or greater than zero if \a a_ is + * lexicographically less than, equal to, or greater than \a b_. + */ +static int +stats_cmp(const void *a_, const void *b_) +{ + const struct StatDesc *a = a_; + const struct StatDesc *b = b_; + return ircd_strcmp(a->sd_name, b->sd_name); +} + +/** Compare a StatDesc's name against a string. + * @param[in] key Pointer to a null-terminated string. + * @param[in] sd_ Pointer to a StatDesc. + * @return Less than, equal to, or greater than zero if \a key is + * lexicographically less than, equal to, or greater than \a + * sd_->sd_name. + */ +static int +stats_search(const void *key, const void *sd_) +{ + const struct StatDesc *sd = sd_; + return ircd_strcmp(key, sd->sd_name); +} + +/** Look up a stats handler. If name_or_char is just one character + * long, use that as a character index; otherwise, look it up by name + * in #statsinfo. + * @param[in] name_or_char Null-terminated string to look up. + * @return The statistics descriptor for \a name_or_char (NULL if none). + */ +const struct StatDesc * +stats_find(const char *name_or_char) +{ + if (!name_or_char[1]) + return statsmap[name_or_char[0] - CHAR_MIN]; + else + return bsearch(name_or_char, statsinfo, statscount, sizeof(statsinfo[0]), stats_search); +} + +/** Build statsmap from the statsinfo array. */ +void +stats_init(void) +{ + struct StatDesc *sd; + + /* Count number of stats entries and sort them. */ + for (statscount = 0, sd = statsinfo; sd->sd_name; sd++, statscount++) {} + qsort(statsinfo, statscount, sizeof(statsinfo[0]), stats_cmp); + + /* Build the mapping */ + for (sd = statsinfo; sd->sd_name; sd++) + { + if (!sd->sd_c) + continue; + else if (sd->sd_flags & STAT_FLAG_CASESENS) + /* case sensitive character... */ + statsmap[sd->sd_c - CHAR_MIN] = sd; + else + { + /* case insensitive--make sure to put in two entries */ + statsmap[ToLower(sd->sd_c) - CHAR_MIN] = sd; + statsmap[ToUpper(sd->sd_c) - CHAR_MIN] = sd; + } + } +}