From patchwork Tue Aug 24 09:51:02 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: QI Fuli X-Patchwork-Id: 12454405 Received: from mail-pg1-f173.google.com (mail-pg1-f173.google.com [209.85.215.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 821912FB2 for ; Tue, 24 Aug 2021 09:51:40 +0000 (UTC) Received: by mail-pg1-f173.google.com with SMTP id s11so19274833pgr.11 for ; Tue, 24 Aug 2021 02:51:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=NlDixsX/4+g0KCYUik51ND3A5hoA0RHkMnEwxESMNiU=; b=utURPE8S93h3hZF0+UNFILcTVdo9dZmnp66BNu+uu0SA4HwPT6+nELODYJgTA5SnDU 0vsP917bssAP3aO7SrcJKJlxZqESx8WPWDtgLIrKtpvDM9JqCx02iolTE33eZmBvPA6Y VkRgxhoKHKvT7twTcMO66sPGLczX/nHRiB4TcNFln4tYHI/YnXGyoZPVEADPdm9M2+vM khJ7VPUrKQXORgV57A8pukKo1z0CSGmHrYDSJRKininm+IWKkJtWfPAvBZ6N23d92AY1 tmfIhgiCBi6m4JqP0TMrqD6TWHnq+64gRaUEZflt8WWPGSAQJ81F2eoRSPb559xceTHg U6yw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=NlDixsX/4+g0KCYUik51ND3A5hoA0RHkMnEwxESMNiU=; b=iQiX/oL3EgjUMy8O4us/NNTeJCnuu5gBEdpZ/ZKRx8RfBno2u/I88A48tj5v2SHCQT vfQqyPgPecSzoLdKZ3PnaaGlawUfOwVcSxJF9PqZ4ILChFmbM7rjx60B9DHBhTyjF08L 9mphXvDBnCtp7cImtpLSkAFabgqcVb1zIYK46jYrx6/F9BqGYZYZYIAWuuZoNBBs7hR1 nD5FEjon7N2aI6VtglpryNLMVAxxSr15LH+G1lbiVAdX2wdgLGgJhT6iUUtbi+mvJtfb 5vcHzyO9sjCjo6DGsPgrv/c7aGgk7G/zO85ygRaeX8IQCy8EYUTcdCAkK6NpK9B/7QiC AcgQ== X-Gm-Message-State: AOAM530gh4LOurLbBP9NxST/VaJCPd5Nsx9XyYxQhs8BONP+p5sSNgEn nsCLztNHH9LxZq9XJNamTSKogOFz7wG8ww== X-Google-Smtp-Source: ABdhPJw/80SInlsv9W1ySHahcVyGfWFSwVwTu23bsnoXrJVMLpGfmC5pGSWd9XvMDQZBatZhZeBOeg== X-Received: by 2002:a62:3812:0:b0:3ec:c9c6:5d1a with SMTP id f18-20020a623812000000b003ecc9c65d1amr3445321pfa.60.1629798698847; Tue, 24 Aug 2021 02:51:38 -0700 (PDT) Received: from localhost.localdomain (125x103x255x1.ap125.ftth.ucom.ne.jp. [125.103.255.1]) by smtp.gmail.com with ESMTPSA id l19sm1873881pjq.10.2021.08.24.02.51.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 02:51:38 -0700 (PDT) From: QI Fuli X-Google-Original-From: QI Fuli To: nvdimm@lists.linux.dev Cc: QI Fuli Subject: [ndctl PATCH v2 1/5] ndctl, util: add iniparser helper Date: Tue, 24 Aug 2021 18:51:02 +0900 Message-Id: <20210824095106.104808-2-qi.fuli@fujitsu.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210824095106.104808-1-qi.fuli@fujitsu.com> References: <20210824095106.104808-1-qi.fuli@fujitsu.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: QI Fuli Add iniparser[1] helper for parsing ndctl global configuration files. [1] https://github.com/ndevilla/iniparser Signed-off-by: QI Fuli --- Makefile.am | 6 +- util/dictionary.c | 383 +++++++++++++++++++++ util/dictionary.h | 175 ++++++++++ util/iniparser.c | 838 ++++++++++++++++++++++++++++++++++++++++++++++ util/iniparser.h | 360 ++++++++++++++++++++ 5 files changed, 1761 insertions(+), 1 deletion(-) create mode 100644 util/dictionary.c create mode 100644 util/dictionary.h create mode 100644 util/iniparser.c create mode 100644 util/iniparser.h diff --git a/Makefile.am b/Makefile.am index 60a1998..235c362 100644 --- a/Makefile.am +++ b/Makefile.am @@ -85,6 +85,10 @@ libutil_a_SOURCES = \ util/size.h \ util/main.h \ util/filter.h \ - util/bitmap.h + util/bitmap.h \ + util/dictionary.c \ + util/dictionary.h \ + util/iniparser.c \ + util/iniparser.h nobase_include_HEADERS = daxctl/libdaxctl.h diff --git a/util/dictionary.c b/util/dictionary.c new file mode 100644 index 0000000..55d63ff --- /dev/null +++ b/util/dictionary.c @@ -0,0 +1,383 @@ +/* SPDX-License-Identifier: MIT */ +/* originally copied from https://github.com/ndevilla/iniparser */ + +/*-------------------------------------------------------------------------*/ +/** + @file dictionary.c + @author N. Devillard + @brief Implements a dictionary for string variables. + + This module implements a simple dictionary object, i.e. a list + of string/string associations. This object is useful to store e.g. + informations retrieved from a configuration file (ini files). +*/ +/*--------------------------------------------------------------------------*/ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ +#include "dictionary.h" + +#include +#include +#include +#include + +/** Maximum value size for integers and doubles. */ +#define MAXVALSZ 1024 + +/** Minimal allocated number of entries in a dictionary */ +#define DICTMINSZ 128 + +/** Invalid key token */ +#define DICT_INVALID_KEY ((char*)-1) + +/*--------------------------------------------------------------------------- + Private functions + ---------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------*/ +/** + @brief Duplicate a string + @param s String to duplicate + @return Pointer to a newly allocated string, to be freed with free() + + This is a replacement for strdup(). This implementation is provided + for systems that do not have it. + */ +/*--------------------------------------------------------------------------*/ +static char * xstrdup(const char * s) +{ + char * t ; + size_t len ; + if (!s) + return NULL ; + + len = strlen(s) + 1 ; + t = (char*) malloc(len) ; + if (t) { + memcpy(t, s, len) ; + } + return t ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Double the size of the dictionary + @param d Dictionary to grow + @return This function returns non-zero in case of failure + */ +/*--------------------------------------------------------------------------*/ +static int dictionary_grow(dictionary * d) +{ + char ** new_val ; + char ** new_key ; + unsigned * new_hash ; + + new_val = (char**) calloc(d->size * 2, sizeof *d->val); + new_key = (char**) calloc(d->size * 2, sizeof *d->key); + new_hash = (unsigned*) calloc(d->size * 2, sizeof *d->hash); + if (!new_val || !new_key || !new_hash) { + /* An allocation failed, leave the dictionary unchanged */ + if (new_val) + free(new_val); + if (new_key) + free(new_key); + if (new_hash) + free(new_hash); + return -1 ; + } + /* Initialize the newly allocated space */ + memcpy(new_val, d->val, d->size * sizeof(char *)); + memcpy(new_key, d->key, d->size * sizeof(char *)); + memcpy(new_hash, d->hash, d->size * sizeof(unsigned)); + /* Delete previous data */ + free(d->val); + free(d->key); + free(d->hash); + /* Actually update the dictionary */ + d->size *= 2 ; + d->val = new_val; + d->key = new_key; + d->hash = new_hash; + return 0 ; +} + +/*--------------------------------------------------------------------------- + Function codes + ---------------------------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ +/** + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. + + This hash function has been taken from an Article in Dr Dobbs Journal. + This is normally a collision-free function, distributing keys evenly. + The key is stored anyway in the struct so that collision can be avoided + by comparing the key itself in last resort. + */ +/*--------------------------------------------------------------------------*/ +unsigned dictionary_hash(const char * key) +{ + size_t len ; + unsigned hash ; + size_t i ; + + if (!key) + return 0 ; + + len = strlen(key); + for (hash=0, i=0 ; i>6) ; + } + hash += (hash <<3); + hash ^= (hash >>11); + hash += (hash <<15); + return hash ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary object. + + This function allocates a new dictionary object of given size and returns + it. If you do not know in advance (roughly) the number of entries in the + dictionary, give size=0. + */ +/*-------------------------------------------------------------------------*/ +dictionary * dictionary_new(size_t size) +{ + dictionary * d ; + + /* If no size was specified, allocate space for DICTMINSZ */ + if (sizesize = size ; + d->val = (char**) calloc(size, sizeof *d->val); + d->key = (char**) calloc(size, sizeof *d->key); + d->hash = (unsigned*) calloc(size, sizeof *d->hash); + } + return d ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void + + Deallocate a dictionary object and all memory associated to it. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_del(dictionary * d) +{ + ssize_t i ; + + if (d==NULL) return ; + for (i=0 ; isize ; i++) { + if (d->key[i]!=NULL) + free(d->key[i]); + if (d->val[i]!=NULL) + free(d->val[i]); + } + free(d->val); + free(d->key); + free(d->hash); + free(d); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. + @param def Default value to return if key not found. + @return 1 pointer to internally allocated character string. + + This function locates a key in a dictionary and returns a pointer to its + value, or the passed 'def' pointer if no such key can be found in + dictionary. The returned character pointer points to data internal to the + dictionary object, you should not try to free it or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * dictionary_get(const dictionary * d, const char * key, const char * def) +{ + unsigned hash ; + ssize_t i ; + + hash = dictionary_hash(key); + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + /* Compare hash */ + if (hash==d->hash[i]) { + /* Compare string, to avoid hash collisions */ + if (!strcmp(key, d->key[i])) { + return d->val[i] ; + } + } + } + return def ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Set a value in a dictionary. + @param d dictionary object to modify. + @param key Key to modify or add. + @param val Value to add. + @return int 0 if Ok, anything else otherwise + + If the given key is found in the dictionary, the associated value is + replaced by the provided one. If the key cannot be found in the + dictionary, it is added to it. + + It is Ok to provide a NULL value for val, but NULL values for the dictionary + or the key are considered as errors: the function will return immediately + in such a case. + + Notice that if you dictionary_set a variable to NULL, a call to + dictionary_get will return a NULL value: the variable will be found, and + its value (NULL) is returned. In other words, setting the variable + content to NULL is equivalent to deleting the variable from the + dictionary. It is not possible (in this implementation) to have a key in + the dictionary without value. + + This function returns non-zero in case of failure. + */ +/*--------------------------------------------------------------------------*/ +int dictionary_set(dictionary * d, const char * key, const char * val) +{ + ssize_t i ; + unsigned hash ; + + if (d==NULL || key==NULL) return -1 ; + + /* Compute hash for this key */ + hash = dictionary_hash(key) ; + /* Find if value is already in dictionary */ + if (d->n>0) { + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (hash==d->hash[i]) { /* Same hash value */ + if (!strcmp(key, d->key[i])) { /* Same key */ + /* Found a value: modify and return */ + if (d->val[i]!=NULL) + free(d->val[i]); + d->val[i] = (val ? xstrdup(val) : NULL); + /* Value has been modified: return */ + return 0 ; + } + } + } + } + /* Add a new value */ + /* See if dictionary needs to grow */ + if (d->n==d->size) { + /* Reached maximum size: reallocate dictionary */ + if (dictionary_grow(d) != 0) + return -1; + } + + /* Insert key in the first empty slot. Start at d->n and wrap at + d->size. Because d->n < d->size this will necessarily + terminate. */ + for (i=d->n ; d->key[i] ; ) { + if(++i == d->size) i = 0; + } + /* Copy key */ + d->key[i] = xstrdup(key); + d->val[i] = (val ? xstrdup(val) : NULL) ; + d->hash[i] = hash; + d->n ++ ; + return 0 ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. + @return void + + This function deletes a key in a dictionary. Nothing is done if the + key cannot be found. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_unset(dictionary * d, const char * key) +{ + unsigned hash ; + ssize_t i ; + + if (key == NULL || d == NULL) { + return; + } + + hash = dictionary_hash(key); + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + /* Compare hash */ + if (hash==d->hash[i]) { + /* Compare string, to avoid hash collisions */ + if (!strcmp(key, d->key[i])) { + /* Found key */ + break ; + } + } + } + if (i>=d->size) + /* Key not found */ + return ; + + free(d->key[i]); + d->key[i] = NULL ; + if (d->val[i]!=NULL) { + free(d->val[i]); + d->val[i] = NULL ; + } + d->hash[i] = 0 ; + d->n -- ; + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void + + Dumps a dictionary onto an opened file pointer. Key pairs are printed out + as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as + output file pointers. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_dump(const dictionary * d, FILE * out) +{ + ssize_t i ; + + if (d==NULL || out==NULL) return ; + if (d->n<1) { + fprintf(out, "empty dictionary\n"); + return ; + } + for (i=0 ; isize ; i++) { + if (d->key[i]) { + fprintf(out, "%20s\t[%s]\n", + d->key[i], + d->val[i] ? d->val[i] : "UNDEF"); + } + } + return ; +} diff --git a/util/dictionary.h b/util/dictionary.h new file mode 100644 index 0000000..4678e38 --- /dev/null +++ b/util/dictionary.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: MIT */ +/* originally copied from https://github.com/ndevilla/iniparser */ + +/*-------------------------------------------------------------------------*/ +/** + @file dictionary.h + @author N. Devillard + @brief Implements a dictionary for string variables. + + This module implements a simple dictionary object, i.e. a list + of string/string associations. This object is useful to store e.g. + informations retrieved from a configuration file (ini files). +*/ +/*--------------------------------------------------------------------------*/ + +#ifndef _DICTIONARY_H_ +#define _DICTIONARY_H_ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*--------------------------------------------------------------------------- + New types + ---------------------------------------------------------------------------*/ + + +/*-------------------------------------------------------------------------*/ +/** + @brief Dictionary object + + This object contains a list of string/string associations. Each + association is identified by a unique string key. Looking up values + in the dictionary is speeded up by the use of a (hopefully collision-free) + hash function. + */ +/*-------------------------------------------------------------------------*/ +typedef struct _dictionary_ { + int n ; /** Number of entries in dictionary */ + ssize_t size ; /** Storage size */ + char ** val ; /** List of string values */ + char ** key ; /** List of string keys */ + unsigned * hash ; /** List of hash values for keys */ +} dictionary ; + + +/*--------------------------------------------------------------------------- + Function prototypes + ---------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------*/ +/** + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. + + This hash function has been taken from an Article in Dr Dobbs Journal. + This is normally a collision-free function, distributing keys evenly. + The key is stored anyway in the struct so that collision can be avoided + by comparing the key itself in last resort. + */ +/*--------------------------------------------------------------------------*/ +unsigned dictionary_hash(const char * key); + +/*-------------------------------------------------------------------------*/ +/** + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary object. + + This function allocates a new dictionary object of given size and returns + it. If you do not know in advance (roughly) the number of entries in the + dictionary, give size=0. + */ +/*--------------------------------------------------------------------------*/ +dictionary * dictionary_new(size_t size); + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void + + Deallocate a dictionary object and all memory associated to it. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_del(dictionary * vd); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. + @param def Default value to return if key not found. + @return 1 pointer to internally allocated character string. + + This function locates a key in a dictionary and returns a pointer to its + value, or the passed 'def' pointer if no such key can be found in + dictionary. The returned character pointer points to data internal to the + dictionary object, you should not try to free it or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * dictionary_get(const dictionary * d, const char * key, const char * def); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Set a value in a dictionary. + @param d dictionary object to modify. + @param key Key to modify or add. + @param val Value to add. + @return int 0 if Ok, anything else otherwise + + If the given key is found in the dictionary, the associated value is + replaced by the provided one. If the key cannot be found in the + dictionary, it is added to it. + + It is Ok to provide a NULL value for val, but NULL values for the dictionary + or the key are considered as errors: the function will return immediately + in such a case. + + Notice that if you dictionary_set a variable to NULL, a call to + dictionary_get will return a NULL value: the variable will be found, and + its value (NULL) is returned. In other words, setting the variable + content to NULL is equivalent to deleting the variable from the + dictionary. It is not possible (in this implementation) to have a key in + the dictionary without value. + + This function returns non-zero in case of failure. + */ +/*--------------------------------------------------------------------------*/ +int dictionary_set(dictionary * vd, const char * key, const char * val); + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. + @return void + + This function deletes a key in a dictionary. Nothing is done if the + key cannot be found. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_unset(dictionary * d, const char * key); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void + + Dumps a dictionary onto an opened file pointer. Key pairs are printed out + as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as + output file pointers. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_dump(const dictionary * d, FILE * out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/util/iniparser.c b/util/iniparser.c new file mode 100644 index 0000000..9e2023a --- /dev/null +++ b/util/iniparser.c @@ -0,0 +1,838 @@ +/* SPDX-License-Identifier: MIT */ +/* originally copied from https://github.com/ndevilla/iniparser */ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.c + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ +/*---------------------------- Includes ------------------------------------*/ +#include +#include +#include "iniparser.h" + +/*---------------------------- Defines -------------------------------------*/ +#define ASCIILINESZ (1024) +#define INI_INVALID_KEY ((char*)-1) + +/*--------------------------------------------------------------------------- + Private to this module + ---------------------------------------------------------------------------*/ +/** + * This enum stores the status for each parsed line (internal use only). + */ +typedef enum _line_status_ { + LINE_UNPROCESSED, + LINE_ERROR, + LINE_EMPTY, + LINE_COMMENT, + LINE_SECTION, + LINE_VALUE +} line_status ; + +/*-------------------------------------------------------------------------*/ +/** + @brief Convert a string to lowercase. + @param in String to convert. + @param out Output buffer. + @param len Size of the out buffer. + @return ptr to the out buffer or NULL if an error occured. + + This function convert a string into lowercase. + At most len - 1 elements of the input string will be converted. + */ +/*--------------------------------------------------------------------------*/ +static const char * strlwc(const char * in, char *out, unsigned len) +{ + unsigned i ; + + if (in==NULL || out == NULL || len==0) return NULL ; + i=0 ; + while (in[i] != '\0' && i < len-1) { + out[i] = (char)tolower((int)in[i]); + i++ ; + } + out[i] = '\0'; + return out ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Duplicate a string + @param s String to duplicate + @return Pointer to a newly allocated string, to be freed with free() + + This is a replacement for strdup(). This implementation is provided + for systems that do not have it. + */ +/*--------------------------------------------------------------------------*/ +static char * xstrdup(const char * s) +{ + char * t ; + size_t len ; + if (!s) + return NULL ; + + len = strlen(s) + 1 ; + t = (char*) malloc(len) ; + if (t) { + memcpy(t, s, len) ; + } + return t ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Remove blanks at the beginning and the end of a string. + @param str String to parse and alter. + @return unsigned New size of the string. + */ +/*--------------------------------------------------------------------------*/ +static unsigned strstrip(char * s) +{ + char *last = NULL ; + char *dest = s; + + if (s==NULL) return 0; + + last = s + strlen(s); + while (isspace((int)*s) && *s) s++; + while (last > s) { + if (!isspace((int)*(last-1))) + break ; + last -- ; + } + *last = (char)0; + + memmove(dest,s,last - s + 1); + return last - s; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Default error callback for iniparser: wraps `fprintf(stderr, ...)`. + */ +/*--------------------------------------------------------------------------*/ +static int default_error_callback(const char *format, ...) +{ + int ret; + va_list argptr; + va_start(argptr, format); + ret = vfprintf(stderr, format, argptr); + va_end(argptr); + return ret; +} + +static int (*iniparser_error_callback)(const char*, ...) = default_error_callback; + +/*-------------------------------------------------------------------------*/ +/** + @brief Configure a function to receive the error messages. + @param errback Function to call. + + By default, the error will be printed on stderr. If a null pointer is passed + as errback the error callback will be switched back to default. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_set_error_callback(int (*errback)(const char *, ...)) +{ + if (errback) { + iniparser_error_callback = errback; + } else { + iniparser_error_callback = default_error_callback; + } +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get number of sections in a dictionary + @param d Dictionary to examine + @return int Number of sections found in dictionary + + This function returns the number of sections found in a dictionary. + The test to recognize sections is done on the string stored in the + dictionary: a section name is given as "section" whereas a key is + stored as "section:key", thus the test looks for entries that do not + contain a colon. + + This clearly fails in the case a section name contains a colon, but + this should simply be avoided. + + This function returns -1 in case of error. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getnsec(const dictionary * d) +{ + int i ; + int nsec ; + + if (d==NULL) return -1 ; + nsec=0 ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (strchr(d->key[i], ':')==NULL) { + nsec ++ ; + } + } + return nsec ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get name for section n in a dictionary. + @param d Dictionary to examine + @param n Section number (from 0 to nsec-1). + @return Pointer to char string + + This function locates the n-th section in a dictionary and returns + its name as a pointer to a string statically allocated inside the + dictionary. Do not free or modify the returned string! + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getsecname(const dictionary * d, int n) +{ + int i ; + int foundsec ; + + if (d==NULL || n<0) return NULL ; + foundsec=0 ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (strchr(d->key[i], ':')==NULL) { + foundsec++ ; + if (foundsec>n) + break ; + } + } + if (foundsec<=n) { + return NULL ; + } + return d->key[i] ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump. + @param f Opened file pointer to dump to. + @return void + + This function prints out the contents of a dictionary, one element by + line, onto the provided file pointer. It is OK to specify @c stderr + or @c stdout as output files. This function is meant for debugging + purposes mostly. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump(const dictionary * d, FILE * f) +{ + int i ; + + if (d==NULL || f==NULL) return ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (d->val[i]!=NULL) { + fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); + } else { + fprintf(f, "[%s]=UNDEF\n", d->key[i]); + } + } + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary to a loadable ini file + @param d Dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given dictionary into a loadable ini file. + It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump_ini(const dictionary * d, FILE * f) +{ + int i ; + int nsec ; + const char * secname ; + + if (d==NULL || f==NULL) return ; + + nsec = iniparser_getnsec(d); + if (nsec<1) { + /* No section in file: dump all keys as they are */ + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + fprintf(f, "%s = %s\n", d->key[i], d->val[i]); + } + return ; + } + for (i=0 ; isize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + fprintf(f, + "%-30s = %s\n", + d->key[j]+seclen+1, + d->val[j] ? d->val[j] : ""); + } + } + fprintf(f, "\n"); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return Number of keys in section + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getsecnkeys(const dictionary * d, const char * s) +{ + int seclen, nkeys ; + char keym[ASCIILINESZ+1]; + int j ; + + nkeys = 0; + + if (d==NULL) return nkeys; + if (! iniparser_find_entry(d, s)) return nkeys; + + seclen = (int)strlen(s); + strlwc(s, keym, sizeof(keym)); + keym[seclen] = ':'; + + for (j=0 ; jsize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) + nkeys++; + } + + return nkeys; + +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @param keys Already allocated array to store the keys in + @return The pointer passed as `keys` argument or NULL in case of error + + This function queries a dictionary and finds all keys in a given section. + The keys argument should be an array of pointers which size has been + determined by calling `iniparser_getsecnkeys` function prior to this one. + + Each pointer in the returned char pointer-to-pointer is pointing to + a string allocated in the dictionary; do not free or modify them. + */ +/*--------------------------------------------------------------------------*/ +const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys) +{ + int i, j, seclen ; + char keym[ASCIILINESZ+1]; + + if (d==NULL || keys==NULL) return NULL; + if (! iniparser_find_entry(d, s)) return NULL; + + seclen = (int)strlen(s); + strlwc(s, keym, sizeof(keym)); + keym[seclen] = ':'; + + i = 0; + + for (j=0 ; jsize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + keys[i] = d->key[j]; + i++; + } + } + + return keys; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key + @param d Dictionary to search + @param key Key string to look for + @param def Default value to return if key not found. + @return pointer to statically allocated character string + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the pointer passed as 'def' is returned. + The returned char pointer is pointing to a string allocated in + the dictionary, do not free or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getstring(const dictionary * d, const char * key, const char * def) +{ + const char * lc_key ; + const char * sval ; + char tmp_str[ASCIILINESZ+1]; + + if (d==NULL || key==NULL) + return def ; + + lc_key = strlwc(key, tmp_str, sizeof(tmp_str)); + sval = dictionary_get(d, lc_key, def); + return sval ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an long int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return long integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + "42" -> 42 + "042" -> 34 (octal -> decimal) + "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==INI_INVALID_KEY) return notfound ; + return strtol(str, NULL, 0); +} + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + "42" -> 42 + "042" -> 34 (octal -> decimal) + "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(const dictionary * d, const char * key, int notfound) +{ + return (int)iniparser_getlongint(d, key, notfound); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a double + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return double + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + */ +/*--------------------------------------------------------------------------*/ +double iniparser_getdouble(const dictionary * d, const char * key, double notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==INI_INVALID_KEY) return notfound ; + return atof(str); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a boolean + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + A true boolean is found if one of the following is matched: + + - A string starting with 'y' + - A string starting with 'Y' + - A string starting with 't' + - A string starting with 'T' + - A string starting with '1' + + A false boolean is found if one of the following is matched: + + - A string starting with 'n' + - A string starting with 'N' + - A string starting with 'f' + - A string starting with 'F' + - A string starting with '0' + + The notfound value returned if no boolean is identified, does not + necessarily have to be 0 or 1. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getboolean(const dictionary * d, const char * key, int notfound) +{ + int ret ; + const char * c ; + + c = iniparser_getstring(d, key, INI_INVALID_KEY); + if (c==INI_INVALID_KEY) return notfound ; + if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { + ret = 1 ; + } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { + ret = 0 ; + } else { + ret = notfound ; + } + return ret; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Finds out if a given entry exists in a dictionary + @param ini Dictionary to search + @param entry Name of the entry to look for + @return integer 1 if entry exists, 0 otherwise + + Finds out if a given entry exists in the dictionary. Since sections + are stored as keys with NULL associated values, this is the only way + of querying for the presence of sections in a dictionary. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_find_entry(const dictionary * ini, const char * entry) +{ + int found=0 ; + if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { + found = 1 ; + } + return found ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Set an entry in a dictionary. + @param ini Dictionary to modify. + @param entry Entry to modify (entry name) + @param val New value to associate to the entry. + @return int 0 if Ok, -1 otherwise. + + If the given entry can be found in the dictionary, it is modified to + contain the provided value. If it cannot be found, the entry is created. + It is Ok to set val to NULL. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_set(dictionary * ini, const char * entry, const char * val) +{ + char tmp_str[ASCIILINESZ+1]; + return dictionary_set(ini, strlwc(entry, tmp_str, sizeof(tmp_str)), val) ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete an entry in a dictionary + @param ini Dictionary to modify + @param entry Entry to delete (entry name) + @return void + + If the given entry can be found, it is deleted from the dictionary. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_unset(dictionary * ini, const char * entry) +{ + char tmp_str[ASCIILINESZ+1]; + dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str))); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Load a single line from an INI file + @param input_line Input line, may be concatenated multi-line input + @param section Output space to store section + @param key Output space to store key + @param value Output space to store value + @return line_status value + */ +/*--------------------------------------------------------------------------*/ +static line_status iniparser_line( + const char * input_line, + char * section, + char * key, + char * value) +{ + line_status sta ; + char * line = NULL; + size_t len ; + + line = xstrdup(input_line); + len = strstrip(line); + + sta = LINE_UNPROCESSED ; + if (len<1) { + /* Empty line */ + sta = LINE_EMPTY ; + } else if (line[0]=='#' || line[0]==';') { + /* Comment line */ + sta = LINE_COMMENT ; + } else if (line[0]=='[' && line[len-1]==']') { + /* Section name */ + sscanf(line, "[%[^]]", section); + strstrip(section); + strlwc(section, section, len); + sta = LINE_SECTION ; + } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 + || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2) { + /* Usual key=value with quotes, with or without comments */ + strstrip(key); + strlwc(key, key, len); + /* Don't strip spaces from values surrounded with quotes */ + sta = LINE_VALUE ; + } else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { + /* Usual key=value without quotes, with or without comments */ + strstrip(key); + strlwc(key, key, len); + strstrip(value); + /* + * sscanf cannot handle '' or "" as empty values + * this is done here + */ + if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { + value[0]=0 ; + } + sta = LINE_VALUE ; + } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2 + || sscanf(line, "%[^=] %[=]", key, value) == 2) { + /* + * Special cases: + * key= + * key=; + * key=# + */ + strstrip(key); + strlwc(key, key, len); + value[0]=0 ; + sta = LINE_VALUE ; + } else { + /* Generate syntax error */ + sta = LINE_ERROR ; + } + + free(line); + return sta ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame) +{ + FILE * in ; + + char line [ASCIILINESZ+1] ; + char section [ASCIILINESZ+1] ; + char key [ASCIILINESZ+1] ; + char tmp [(ASCIILINESZ * 2) + 2] ; + char val [ASCIILINESZ+1] ; + + int last=0 ; + int len ; + int lineno=0 ; + int errs=0; + int mem_err=0; + + dictionary * dict ; + + if ((in=fopen(ininame, "r"))==NULL) { + iniparser_error_callback("iniparser: cannot open %s\n", ininame); + return NULL ; + } + + dict = dictionary_new(0) ; + if (!dict) { + fclose(in); + return NULL ; + } + + memset(line, 0, ASCIILINESZ); + memset(section, 0, ASCIILINESZ); + memset(key, 0, ASCIILINESZ); + memset(val, 0, ASCIILINESZ); + last=0 ; + + while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { + lineno++ ; + len = (int)strlen(line)-1; + if (len<=0) + continue; + /* Safety check against buffer overflows */ + if (line[len]!='\n' && !feof(in)) { + iniparser_error_callback( + "iniparser: input line too long in %s (%d)\n", + ininame, + lineno); + dictionary_del(dict); + fclose(in); + return NULL ; + } + /* Get rid of \n and spaces at end of line */ + while ((len>=0) && + ((line[len]=='\n') || (isspace(line[len])))) { + line[len]=0 ; + len-- ; + } + if (len < 0) { /* Line was entirely \n and/or spaces */ + len = 0; + } + /* Detect multi-line */ + if (line[len]=='\\') { + /* Multi-line value */ + last=len ; + continue ; + } else { + last=0 ; + } + switch (iniparser_line(line, section, key, val)) { + case LINE_EMPTY: + case LINE_COMMENT: + break ; + + case LINE_SECTION: + mem_err = dictionary_set(dict, section, NULL); + break ; + + case LINE_VALUE: + sprintf(tmp, "%s:%s", section, key); + mem_err = dictionary_set(dict, tmp, val); + break ; + + case LINE_ERROR: + iniparser_error_callback( + "iniparser: syntax error in %s (%d):\n-> %s\n", + ininame, + lineno, + line); + errs++ ; + break; + + default: + break ; + } + memset(line, 0, ASCIILINESZ); + last=0; + if (mem_err<0) { + iniparser_error_callback("iniparser: memory allocation failure\n"); + break ; + } + } + if (errs) { + dictionary_del(dict); + dict = NULL ; + } + fclose(in); + return dict ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Free all memory associated to an ini dictionary + @param d Dictionary to free + @return void + + Free all memory associated to an ini dictionary. + It is mandatory to call this function before the dictionary object + gets out of the current context. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_freedict(dictionary * d) +{ + dictionary_del(d); +} diff --git a/util/iniparser.h b/util/iniparser.h new file mode 100644 index 0000000..9e30119 --- /dev/null +++ b/util/iniparser.h @@ -0,0 +1,360 @@ +/* SPDX-License-Identifier: MIT */ +/* originally copied from https://github.com/ndevilla/iniparser */ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.h + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ + +#ifndef _INIPARSER_H_ +#define _INIPARSER_H_ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ + +#include +#include +#include + +/* + * The following #include is necessary on many Unixes but not Linux. + * It is not needed for Windows platforms. + * Uncomment it if needed. + */ +/* #include */ + +#include "dictionary.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*-------------------------------------------------------------------------*/ +/** + @brief Configure a function to receive the error messages. + @param errback Function to call. + + By default, the error will be printed on stderr. If a null pointer is passed + as errback the error callback will be switched back to default. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_set_error_callback(int (*errback)(const char *, ...)); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get number of sections in a dictionary + @param d Dictionary to examine + @return int Number of sections found in dictionary + + This function returns the number of sections found in a dictionary. + The test to recognize sections is done on the string stored in the + dictionary: a section name is given as "section" whereas a key is + stored as "section:key", thus the test looks for entries that do not + contain a colon. + + This clearly fails in the case a section name contains a colon, but + this should simply be avoided. + + This function returns -1 in case of error. + */ +/*--------------------------------------------------------------------------*/ + +int iniparser_getnsec(const dictionary * d); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get name for section n in a dictionary. + @param d Dictionary to examine + @param n Section number (from 0 to nsec-1). + @return Pointer to char string + + This function locates the n-th section in a dictionary and returns + its name as a pointer to a string statically allocated inside the + dictionary. Do not free or modify the returned string! + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ + +const char * iniparser_getsecname(const dictionary * d, int n); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary to a loadable ini file + @param d Dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given dictionary into a loadable ini file. + It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dump_ini(const dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary section to a loadable ini file + @param d Dictionary to dump + @param s Section name of dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given section of a given dictionary into a loadable ini + file. It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump. + @param f Opened file pointer to dump to. + @return void + + This function prints out the contents of a dictionary, one element by + line, onto the provided file pointer. It is OK to specify @c stderr + or @c stdout as output files. This function is meant for debugging + purposes mostly. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump(const dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return Number of keys in section + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getsecnkeys(const dictionary * d, const char * s); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @param keys Already allocated array to store the keys in + @return The pointer passed as `keys` argument or NULL in case of error + + This function queries a dictionary and finds all keys in a given section. + The keys argument should be an array of pointers which size has been + determined by calling `iniparser_getsecnkeys` function prior to this one. + + Each pointer in the returned char pointer-to-pointer is pointing to + a string allocated in the dictionary; do not free or modify them. + */ +/*--------------------------------------------------------------------------*/ +const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key + @param d Dictionary to search + @param key Key string to look for + @param def Default value to return if key not found. + @return pointer to statically allocated character string + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the pointer passed as 'def' is returned. + The returned char pointer is pointing to a string allocated in + the dictionary, do not free or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getstring(const dictionary * d, const char * key, const char * def); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(const dictionary * d, const char * key, int notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an long int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + */ +/*--------------------------------------------------------------------------*/ +long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a double + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return double + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + */ +/*--------------------------------------------------------------------------*/ +double iniparser_getdouble(const dictionary * d, const char * key, double notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a boolean + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + A true boolean is found if one of the following is matched: + + - A string starting with 'y' + - A string starting with 'Y' + - A string starting with 't' + - A string starting with 'T' + - A string starting with '1' + + A false boolean is found if one of the following is matched: + + - A string starting with 'n' + - A string starting with 'N' + - A string starting with 'f' + - A string starting with 'F' + - A string starting with '0' + + The notfound value returned if no boolean is identified, does not + necessarily have to be 0 or 1. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getboolean(const dictionary * d, const char * key, int notfound); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Set an entry in a dictionary. + @param ini Dictionary to modify. + @param entry Entry to modify (entry name) + @param val New value to associate to the entry. + @return int 0 if Ok, -1 otherwise. + + If the given entry can be found in the dictionary, it is modified to + contain the provided value. If it cannot be found, the entry is created. + It is Ok to set val to NULL. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_set(dictionary * ini, const char * entry, const char * val); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete an entry in a dictionary + @param ini Dictionary to modify + @param entry Entry to delete (entry name) + @return void + + If the given entry can be found, it is deleted from the dictionary. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_unset(dictionary * ini, const char * entry); + +/*-------------------------------------------------------------------------*/ +/** + @brief Finds out if a given entry exists in a dictionary + @param ini Dictionary to search + @param entry Name of the entry to look for + @return integer 1 if entry exists, 0 otherwise + + Finds out if a given entry exists in the dictionary. Since sections + are stored as keys with NULL associated values, this is the only way + of querying for the presence of sections in a dictionary. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_find_entry(const dictionary * ini, const char * entry) ; + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame); + +/*-------------------------------------------------------------------------*/ +/** + @brief Free all memory associated to an ini dictionary + @param d Dictionary to free + @return void + + Free all memory associated to an ini dictionary. + It is mandatory to call this function before the dictionary object + gets out of the current context. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_freedict(dictionary * d); + +#ifdef __cplusplus +} +#endif + +#endif From patchwork Tue Aug 24 09:51:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: QI Fuli X-Patchwork-Id: 12454399 Received: from mail-pl1-f173.google.com (mail-pl1-f173.google.com [209.85.214.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 713073FC1 for ; Tue, 24 Aug 2021 09:51:40 +0000 (UTC) Received: by mail-pl1-f173.google.com with SMTP id j2so7491270pll.1 for ; Tue, 24 Aug 2021 02:51:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=o2PXGiV96ks98ZyRx4ECxeT1W/P4gZfz+2Jb9Vgi1H4=; b=cCTp+2+uDUVUMFE4JVHdT53l8g7Ob9ozf3TN6iLWv7WlnZjh91x3SuNsyi/X7BBzVP wRuA+8vIRFAyZ5YDjPl/Duy4nycRTp4qDnSikyfgfOf8QEsMMkuyWfUdvHL5DFb33NC3 0XTPrzLk43FMct5cm28tvKJDwd3Y0rOi/0BsFMWSAXZCZW4kMVJ3+oXdWGP4COCYbh2P DyPF/FI2exukC5v0LSnjM0AE49jG0/Hdg0xtAFSSsi6Onm9znDw1DkFM4OetnKn3Ugno CXalFp1WsoI/taMphBl0nSEBa3mcpYu5HR1qEQrUttkDWDsnY1RO0dUwx1rKVteQh64B xSaA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=o2PXGiV96ks98ZyRx4ECxeT1W/P4gZfz+2Jb9Vgi1H4=; b=NeXrM6+nb5j+1wqx6O3VNDXa8mmJftr1+wR7meLgS8CRoqxJpl7cVPbhkoUIH5pI13 J4YIvHfp5rDTfuNuRxVDe3BQY2qvOMhSGlspYkolXKYcHp/CfA5T7QjAgJg3rbuJqaP9 qOuMUbGxlPt0VQ/Kml9bcd5kb+mgWKUFdKBqf5CYsbCrnkjMg6tO4XDIBpMDKgM9/Alo ilmRZOHyHBHF99PYlTyqSra4hZ/T6gKRcYF4aUPY/gJCKjBIj8PghUP0IfPp58ZiSvSj gXQASICt25ZjlurNp8Ap0ze8aVVpBxDiSvTrMWC+sTdP5xOB2TIQiCe8L8nMjQ+ovrXv xPDw== X-Gm-Message-State: AOAM5325YYCDuKxBQey1Sl2McAF1e8s3abYi2viRqcjhctBEFksMgsOa 3G28/6I8trNgEsL++fGsQbdEq9rn017QXQ== X-Google-Smtp-Source: ABdhPJzkxZQuw1v0a3/cN6EQue5+5l2jap03coMiVukxZiw0Zb494NFj2UbXRCpi9s4Hi5vDn7QTIg== X-Received: by 2002:a17:902:690a:b0:12d:86cf:d981 with SMTP id j10-20020a170902690a00b0012d86cfd981mr32755236plk.39.1629798700105; Tue, 24 Aug 2021 02:51:40 -0700 (PDT) Received: from localhost.localdomain (125x103x255x1.ap125.ftth.ucom.ne.jp. [125.103.255.1]) by smtp.gmail.com with ESMTPSA id l19sm1873881pjq.10.2021.08.24.02.51.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 02:51:39 -0700 (PDT) From: QI Fuli X-Google-Original-From: QI Fuli To: nvdimm@lists.linux.dev Cc: QI Fuli Subject: [ndctl PATCH v2 2/5] ndctl, util: add parse-configs helper Date: Tue, 24 Aug 2021 18:51:03 +0900 Message-Id: <20210824095106.104808-3-qi.fuli@fujitsu.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210824095106.104808-1-qi.fuli@fujitsu.com> References: <20210824095106.104808-1-qi.fuli@fujitsu.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: QI Fuli Add parse-config util to help ndctl commands parse ndctl global configuration files. Signed-off-by: QI Fuli --- Makefile.am | 2 ++ util/parse-configs.c | 82 ++++++++++++++++++++++++++++++++++++++++++++ util/parse-configs.h | 34 ++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 util/parse-configs.c create mode 100644 util/parse-configs.h diff --git a/Makefile.am b/Makefile.am index 235c362..af55f0e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,6 +70,8 @@ noinst_LIBRARIES += libutil.a libutil_a_SOURCES = \ util/parse-options.c \ util/parse-options.h \ + util/parse-configs.c \ + util/parse-configs.h \ util/usage.c \ util/size.c \ util/main.c \ diff --git a/util/parse-configs.c b/util/parse-configs.c new file mode 100644 index 0000000..44dcff4 --- /dev/null +++ b/util/parse-configs.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2021, FUJITSU LIMITED. ALL rights reserved. + +#include +#include +#include +#include + +static void set_str_val(const char **value, const char *val) +{ + struct strbuf buf = STRBUF_INIT; + size_t len = *value ? strlen(*value) : 0; + + if (!val) + return; + + if (len) { + strbuf_add(&buf, *value, len); + strbuf_addstr(&buf, " "); + } + strbuf_addstr(&buf, val); + *value = strbuf_detach(&buf, NULL); +} + +static int parse_config_file(const char *config_file, + const struct config *configs) +{ + dictionary *dic; + + dic = iniparser_load(config_file); + if (!dic) + return -errno; + + for (; configs->type != CONFIG_END; configs++) { + switch (configs->type) { + case CONFIG_STRING: + set_str_val((const char **)configs->value, + iniparser_getstring(dic, + configs->key, configs->defval)); + break; + case MONITOR_CALLBACK: + case CONFIG_END: + break; + } + } + + iniparser_freedict(dic); + return 0; +} + +int parse_configs_prefix(const char *__config_files, const char *prefix, + const struct config *configs) +{ + char *config_files, *save; + const char *config_file; + int rc; + + config_files = strdup(__config_files); + if (!config_files) + return -ENOMEM; + + for (config_file = strtok_r(config_files, " ", &save); config_file; + config_file = strtok_r(NULL, " ", &save)) { + + if (strncmp(config_file, "./", 2) != 0) + fix_filename(prefix, &config_file); + + if ((configs->type == MONITOR_CALLBACK) && + (strcmp(config_file, configs->key) == 0)) + rc = configs->callback(configs, configs->key); + else + rc = parse_config_file(config_file, configs); + + if (rc) + goto end; + } + + end: + free(config_files); + return rc; + +} diff --git a/util/parse-configs.h b/util/parse-configs.h new file mode 100644 index 0000000..f70f58f --- /dev/null +++ b/util/parse-configs.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2021, FUJITSU LIMITED. ALL rights reserved. + +#include +#include +#include + +enum parse_conf_type { + CONFIG_STRING, + CONFIG_END, + MONITOR_CALLBACK, +}; + +struct config; +typedef int parse_conf_cb(const struct config *, const char *config_file); + +struct config { + enum parse_conf_type type; + const char *key; + void *value; + void *defval; + parse_conf_cb *callback; +}; + +#define check_vtype(v, type) ( BUILD_BUG_ON_ZERO(!__builtin_types_compatible_p(typeof(v), type)) + v ) + +#define CONF_END() { .type = CONFIG_END } +#define CONF_STR(k,v,d) \ + { .type = CONFIG_STRING, .key = (k), .value = check_vtype(v, const char **), .defval = (d) } +#define CONF_MONITOR(k,f) \ + { .type = MONITOR_CALLBACK, .key = (k), .callback = (f)} + +int parse_configs_prefix(const char *__config_file, const char *prefix, + const struct config *configs); From patchwork Tue Aug 24 09:51:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: QI Fuli X-Patchwork-Id: 12454403 Received: from mail-pg1-f170.google.com (mail-pg1-f170.google.com [209.85.215.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C10743FC1 for ; Tue, 24 Aug 2021 09:51:41 +0000 (UTC) Received: by mail-pg1-f170.google.com with SMTP id w8so19276544pgf.5 for ; Tue, 24 Aug 2021 02:51:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=alQ14dA2PsFivJXfOGl5xdOoK6JPuQfZK0f7LHKGKlk=; b=ZBWOX2Ybw6UhYbpRdxc2EgBcZxDsFCVoaN3bxdrO65mOMp10a6IEKipICNQgp6y8wE wdSjd7TcEXs84N9x+pWlvLMHiExzRTNCZuvfnGkdmxdhAerYR67dkXfFneKL3mPWRs0h b8YJdTVT/vhkpHnjnBW9mXzkAF3hgQDjSZxZrzD/hqSy8B3hnzeSZn0rNqu9KH1Bkjoe PiRGREJttTKOcbhFHjPQXWAp22qj+HdWcIusSAECPzkjH7QbMuxZlXw5zSuoyYX6dH6y Y3cty9FnwAmZDzu9YF/uy75FIvJfcnoU+0cd+R+QpKlBVgwvIiz3NDQKrzHy/kRzTMWE qq3g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=alQ14dA2PsFivJXfOGl5xdOoK6JPuQfZK0f7LHKGKlk=; b=Rnt7Uks9RWglHN6AJF12ZMWTNNMxYy7ESxy9gELglEUPAhpzV0AqCjng0WTs+3IqZO fTKvOUjcoT3UfLApk0R0NC2PS1GuiKzAAqMhqmP0NC5no/RJYOn3GC6WXbdHcQ5lFrSb EldU4eymovGjz2E/i5VlvJXLuJVL8arkA1zxl+HG1jeQAxS1sDADQGSvHNtdnCEnxiPx sTQ7PITMVgkxcYMj4QknxjB5bjTpSis81Ap/wEuON8CkBranPzp7rw1O/4sF5DqUHWJ7 5otR5X+0OqckOIhdxeHP7eQR1CRaezqDIUsn3A78CwtJND9Wcm6sv9Cs34pWEcu+L6lI 4EVw== X-Gm-Message-State: AOAM533s7BsoZklqBPtr5j9SjhBoZlyTF6OcnQsJyOakGDkCRBfRtEfP bil3iE59kK6lbhLARjTwbQEGMr+stFmwoA== X-Google-Smtp-Source: ABdhPJyUxKt6qGsKAj0BEzCR6ZUkBzhiOOH6jIsvzS5S2RI/ddzDxO79J8snvz4biEN49X9RXQ++9Q== X-Received: by 2002:a05:6a00:22cc:b0:3be:dcd5:b012 with SMTP id f12-20020a056a0022cc00b003bedcd5b012mr38066733pfj.61.1629798701424; Tue, 24 Aug 2021 02:51:41 -0700 (PDT) Received: from localhost.localdomain (125x103x255x1.ap125.ftth.ucom.ne.jp. [125.103.255.1]) by smtp.gmail.com with ESMTPSA id l19sm1873881pjq.10.2021.08.24.02.51.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 02:51:41 -0700 (PDT) From: QI Fuli X-Google-Original-From: QI Fuli To: nvdimm@lists.linux.dev Cc: QI Fuli Subject: [ndctl PATCH v2 3/5] ndctl: make ndctl support configuration files Date: Tue, 24 Aug 2021 18:51:04 +0900 Message-Id: <20210824095106.104808-4-qi.fuli@fujitsu.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210824095106.104808-1-qi.fuli@fujitsu.com> References: <20210824095106.104808-1-qi.fuli@fujitsu.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: QI Fuli Add ndctl_configs to ndctl_ctx for supporting ndctl global configuration files. All files with .conf suffix under {sysconfdir}/ndctl can be regarded as global configuration files that all ndctl commands can refer to. Add ndctl_set_configs() public function for setting ndctl default configuration files' path. Add ndctl_get_configs() public function for getting ndctl configuration files' path form ndctl_ctx. Signed-off-by: QI Fuli --- configure.ac | 4 ++-- ndctl/Makefile.am | 5 ++-- ndctl/lib/Makefile.am | 4 ++++ ndctl/lib/libndctl.c | 52 ++++++++++++++++++++++++++++++++++++++++++ ndctl/lib/libndctl.sym | 2 ++ ndctl/lib/private.h | 1 + ndctl/libndctl.h | 2 ++ ndctl/ndctl.c | 1 + 8 files changed, 67 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index dc39dbe..42a66e1 100644 --- a/configure.ac +++ b/configure.ac @@ -171,9 +171,9 @@ fi AC_SUBST([systemd_unitdir]) AM_CONDITIONAL([ENABLE_SYSTEMD_UNITS], [test "x$with_systemd" = "xyes"]) -ndctl_monitorconfdir=${sysconfdir}/ndctl +ndctl_confdir=${sysconfdir}/ndctl ndctl_monitorconf=monitor.conf -AC_SUBST([ndctl_monitorconfdir]) +AC_SUBST([ndctl_confdir]) AC_SUBST([ndctl_monitorconf]) daxctl_modprobe_datadir=${datadir}/daxctl diff --git a/ndctl/Makefile.am b/ndctl/Makefile.am index a63b1e0..1caa031 100644 --- a/ndctl/Makefile.am +++ b/ndctl/Makefile.am @@ -7,8 +7,9 @@ BUILT_SOURCES = config.h config.h: $(srcdir)/Makefile.am $(AM_V_GEN) echo "/* Autogenerated by ndctl/Makefile.am */" >$@ && \ echo '#define NDCTL_CONF_FILE \ - "$(ndctl_monitorconfdir)/$(ndctl_monitorconf)"' >>$@ + "$(ndctl_confdir)/$(ndctl_monitorconf)"' >>$@ $(AM_V_GEN) echo '#define NDCTL_KEYS_DIR "$(ndctl_keysdir)"' >>$@ + $(AM_V_GEN) echo '#define NDCTL_CONF_DIR "$(ndctl_confdir)"' >>$@ ndctl_SOURCES = ndctl.c \ builtin.h \ @@ -73,7 +74,7 @@ ndctl_SOURCES += ../test/libndctl.c \ test.c endif -monitor_configdir = $(ndctl_monitorconfdir) +monitor_configdir = $(ndctl_confdir) monitor_config_DATA = $(ndctl_monitorconf) if ENABLE_SYSTEMD_UNITS diff --git a/ndctl/lib/Makefile.am b/ndctl/lib/Makefile.am index e15bb22..f741c44 100644 --- a/ndctl/lib/Makefile.am +++ b/ndctl/lib/Makefile.am @@ -14,6 +14,10 @@ libndctl_la_SOURCES =\ ../../util/log.h \ ../../util/sysfs.c \ ../../util/sysfs.h \ + ../../util/strbuf.h \ + ../../util/strbuf.c \ + ../../util/wrapper.c \ + ../../util/usage.c \ ../../util/fletcher.h \ dimm.c \ inject.c \ diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c index 536e142..db2e38b 100644 --- a/ndctl/lib/libndctl.c +++ b/ndctl/lib/libndctl.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -265,6 +266,56 @@ NDCTL_EXPORT void ndctl_set_userdata(struct ndctl_ctx *ctx, void *userdata) ctx->userdata = userdata; } +static int filter_conf(const struct dirent *dir) +{ + if (!dir) + return 0; + + if (dir->d_type == DT_REG) { + const char *ext = strrchr(dir->d_name, '.'); + if ((!ext) || (ext == dir->d_name)) + return 0; + if (strcmp(ext, ".conf") == 0) + return 1; + } + + return 0; +} + +NDCTL_EXPORT void ndctl_set_configs(struct ndctl_ctx **ctx, char *conf_dir) +{ + struct dirent **namelist; + struct strbuf value = STRBUF_INIT; + int rc; + + if ((!ctx) || (!conf_dir)) + return; + + rc = scandir(conf_dir, &namelist, filter_conf, alphasort); + if (rc == -1) { + perror("scandir"); + return; + } + + while (rc--) { + if (value.len) + strbuf_addstr(&value, " "); + strbuf_addstr(&value, conf_dir); + strbuf_addstr(&value, "/"); + strbuf_addstr(&value, namelist[rc]->d_name); + free(namelist[rc]); + } + (*ctx)->configs = strbuf_detach(&value, NULL); + free(namelist); +} + +NDCTL_EXPORT const char *ndctl_get_configs(struct ndctl_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + return ctx->configs; +} + /** * ndctl_new - instantiate a new library context * @ctx: context to establish @@ -331,6 +382,7 @@ NDCTL_EXPORT int ndctl_new(struct ndctl_ctx **ctx) c->daxctl_ctx = daxctl_ctx; return 0; + err_ctx: daxctl_unref(daxctl_ctx); err_daxctl: diff --git a/ndctl/lib/libndctl.sym b/ndctl/lib/libndctl.sym index 58afb74..5bd867d 100644 --- a/ndctl/lib/libndctl.sym +++ b/ndctl/lib/libndctl.sym @@ -454,4 +454,6 @@ LIBNDCTL_25 { LIBNDCTL_26 { ndctl_bus_nfit_translate_spa; + ndctl_set_configs; + ndctl_get_configs; } LIBNDCTL_25; diff --git a/ndctl/lib/private.h b/ndctl/lib/private.h index 8f4510e..f4ca71f 100644 --- a/ndctl/lib/private.h +++ b/ndctl/lib/private.h @@ -129,6 +129,7 @@ struct ndctl_ctx { int regions_init; void *userdata; struct list_head busses; + const char *configs; int busses_init; struct udev *udev; struct udev_queue *udev_queue; diff --git a/ndctl/libndctl.h b/ndctl/libndctl.h index 87d07b7..7ab7691 100644 --- a/ndctl/libndctl.h +++ b/ndctl/libndctl.h @@ -92,6 +92,8 @@ int ndctl_get_log_priority(struct ndctl_ctx *ctx); void ndctl_set_log_priority(struct ndctl_ctx *ctx, int priority); void ndctl_set_userdata(struct ndctl_ctx *ctx, void *userdata); void *ndctl_get_userdata(struct ndctl_ctx *ctx); +void ndctl_set_configs(struct ndctl_ctx **ctx, char *conf_dir); +const char *ndctl_get_configs(struct ndctl_ctx *ctx); enum ndctl_persistence_domain { PERSISTENCE_NONE = 0, diff --git a/ndctl/ndctl.c b/ndctl/ndctl.c index 31d2c5e..0f00b04 100644 --- a/ndctl/ndctl.c +++ b/ndctl/ndctl.c @@ -125,6 +125,7 @@ int main(int argc, const char **argv) rc = ndctl_new(&ctx); if (rc) goto out; + ndctl_set_configs(&ctx, NDCTL_CONF_DIR); main_handle_internal_command(argc, argv, ctx, commands, ARRAY_SIZE(commands), PROG_NDCTL); ndctl_unref(ctx); From patchwork Tue Aug 24 09:51:05 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: QI Fuli X-Patchwork-Id: 12454409 Received: from mail-pl1-f176.google.com (mail-pl1-f176.google.com [209.85.214.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0A0DF3FC1 for ; Tue, 24 Aug 2021 09:51:42 +0000 (UTC) Received: by mail-pl1-f176.google.com with SMTP id j2so7491336pll.1 for ; Tue, 24 Aug 2021 02:51:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=ex53nSYjngYSyxpThYpQZDnvKgvgMewbuv3AG4+XhFY=; b=bbfxcwkSw5bRZJP8w2KHYHRReSxlR+iL1tgbM3XD3OJtG8z+zkvhMoifFXc38Dt/UL cRbRn3YHIQVxiJzXuzCAQmnjRvz0mBwfYswBiX/byisQqQmUZeGzDjWt+EKkhJWsQTtK FEr2X3EwhdX2crI/fsMuHCmvNS9KzlDYNZTiM4YFqzcyu6fAXoMFhZrDJHPhNHgOSOM+ EQZbpJix1j/kB03QDegM77r65Tj0Tv83IvqOAWrLOItgqWvmNf2pdPiiq5C8wWd/419c D6Bs1iP/nS6n43IQ7OQC//qx6w6ovaZzXmu5/8a9lh2gk8XOOXUa/85kJHEI6gX4G5aN WY9Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=ex53nSYjngYSyxpThYpQZDnvKgvgMewbuv3AG4+XhFY=; b=BmnWvL7P+fdGiveJRd5nrz6WeJXB0GQ3m+5Ki6FSUIBt1AU+unqUgzZBnmLeWi8wrt 1sD40gGoSknUNzZwt5sd2nD2tWUrpekhO56M6P0Xecpu//BDpusxC8WYiKbxYjQshmsz EVeQzmeZWsC5xQ+3tmM/cZv0BSUQrRcNUZ5KS5UPxn4QPb0PqnHjxf6drvmDUVqUDgcI sxEEXRtMC5Dj/n6qAu0lYB2nfnzE6vi2gkdn2CkM4uBbPX0p7DbFfoNqAVNGX0Bh4qOD DvZyxRmDnPXVj120RJc6hS0l1D4y5v7Be3cBUTwn6KPsc+juHUTeNwqTk17nhV14jDhU ltsg== X-Gm-Message-State: AOAM530FONFLXdx+YYABM8wmEJbtZYjGpczNu+APgm41sCY7W6t/XgwT QiBgLWy4vpB/VLUqxRr9Y/MB1p+sDEvudw== X-Google-Smtp-Source: ABdhPJwbGWdYtyr87+mqMDJJEBcB8NrSBxpdTt681NjpcnsJoKVpdQSENFiewWydpSqdEuxPe/gUgQ== X-Received: by 2002:a17:902:7682:b029:12d:3a69:c6cb with SMTP id m2-20020a1709027682b029012d3a69c6cbmr32741008pll.65.1629798702667; Tue, 24 Aug 2021 02:51:42 -0700 (PDT) Received: from localhost.localdomain (125x103x255x1.ap125.ftth.ucom.ne.jp. [125.103.255.1]) by smtp.gmail.com with ESMTPSA id l19sm1873881pjq.10.2021.08.24.02.51.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 02:51:42 -0700 (PDT) From: QI Fuli X-Google-Original-From: QI Fuli To: nvdimm@lists.linux.dev Cc: QI Fuli Subject: [ndctl PATCH v2 4/5] ndctl, config: add the default ndctl configuration file Date: Tue, 24 Aug 2021 18:51:05 +0900 Message-Id: <20210824095106.104808-5-qi.fuli@fujitsu.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210824095106.104808-1-qi.fuli@fujitsu.com> References: <20210824095106.104808-1-qi.fuli@fujitsu.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: QI Fuli Install ndctl/ndctl.conf as the default ndctl configuration file. Signed-off-by: QI Fuli --- configure.ac | 2 ++ ndctl/Makefile.am | 4 +++- ndctl/ndctl.conf | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 ndctl/ndctl.conf diff --git a/configure.ac b/configure.ac index 42a66e1..9e1c6db 100644 --- a/configure.ac +++ b/configure.ac @@ -172,8 +172,10 @@ AC_SUBST([systemd_unitdir]) AM_CONDITIONAL([ENABLE_SYSTEMD_UNITS], [test "x$with_systemd" = "xyes"]) ndctl_confdir=${sysconfdir}/ndctl +ndctl_conf=ndctl.conf ndctl_monitorconf=monitor.conf AC_SUBST([ndctl_confdir]) +AC_SUBST([ndctl_conf]) AC_SUBST([ndctl_monitorconf]) daxctl_modprobe_datadir=${datadir}/daxctl diff --git a/ndctl/Makefile.am b/ndctl/Makefile.am index 1caa031..fceb3ab 100644 --- a/ndctl/Makefile.am +++ b/ndctl/Makefile.am @@ -43,7 +43,7 @@ keys_configdir = $(ndctl_keysdir) keys_config_DATA = $(ndctl_keysreadme) endif -EXTRA_DIST += keys.readme monitor.conf ndctl-monitor.service +EXTRA_DIST += keys.readme monitor.conf ndctl-monitor.service ndctl.conf if ENABLE_DESTRUCTIVE ndctl_SOURCES += ../test/blk_namespaces.c \ @@ -74,6 +74,8 @@ ndctl_SOURCES += ../test/libndctl.c \ test.c endif +ndctl_configdir = $(ndctl_confdir) +ndctl_config_DATA = $(ndctl_conf) monitor_configdir = $(ndctl_confdir) monitor_config_DATA = $(ndctl_monitorconf) diff --git a/ndctl/ndctl.conf b/ndctl/ndctl.conf new file mode 100644 index 0000000..04d322d --- /dev/null +++ b/ndctl/ndctl.conf @@ -0,0 +1,56 @@ +# This is the default ndctl configuration file. It contains the +# configuration directives that give ndctl instructions. +# Ndctl supports multiple configuration files. All files with the +# .conf suffix under {sysconfdir}/ndctl can be regarded as ndctl +# configuration files. + +# In this file, lines starting with a hash (#) are comments. +# The configurations should be in a [section] and follow = +# style. Multiple space-separated values are allowed, but except the +# following characters: : ? / \ % " ' $ & ! * { } [ ] ( ) = < > @ + +[core] +# The values in [core] section work for all ndctl sub commands. +# dimm = all +# bus = all +# region = all +# namespace = all + +[monitor] +# The values in [monitor] section work for ndctl monitor. +# You can change the configuration of ndctl monitor by editing this +# file or use [--config-file=] option to override this one. +# The changed value will work after restart ndctl monitor service. + +# The objects to monitor are filtered via dimm's name by setting key "dimm". +# If this value is different from the value of [--dimm=] option, +# both of the values will work. +# dimm = all + +# The objects to monitor are filtered via its parent bus by setting key "bus". +# If this value is different from the value of [--bus=] option, +# both of the values will work. +# bus = all + +# The objects to monitor are filtered via region by setting key "region". +# If this value is different from the value of [--region=] option, +# both of the values will work. +# region = all + +# The objects to monitor are filtered via namespace by setting key "namespace". +# If this value is different from the value of [--namespace=] option, +# both of the values will work. +# namespace = all + +# The DIMM events to monitor are filtered via event type by setting key +# "dimm-event". If this value is different from the value of +# [--dimm-event=] option, both of the values will work. +# dimm-event = all + +# Users can choose to output the notifications to syslog (log=syslog), +# to standard output (log=standard) or to write into a special file (log=) +# by setting key "log". If this value is in conflict with the value of +# [--log=] option, this value will be ignored. +# Note: Setting value to "standard" or relative path for will not work +# when running moniotr as a daemon. +# log = /var/log/ndctl/monitor.log From patchwork Tue Aug 24 09:51:06 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: QI Fuli X-Patchwork-Id: 12454407 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 749183FD3 for ; Tue, 24 Aug 2021 09:51:44 +0000 (UTC) Received: by mail-pg1-f178.google.com with SMTP id x4so19266854pgh.1 for ; Tue, 24 Aug 2021 02:51:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=f1D2+AEArEhW2gM4q5VlXsUVZD8mb0A6TWokPtbcWJc=; b=mEHrLeSX2RRGqAivmK0q7pw0VWpsYu+fi/rjC4rXeFLN3Lhk0ELpwt1mJmWO7YNo6K NBtVgOXvk8EZ52SJtA1ihuVFc/alMndBcrTuO/WAo9wWsGMCF2AoJa2DaSUht/xZ0f6P iZBDImGgB8OOTGY5qguDVLsnJJgwNhfGEyafo/50kmMpkp/9In67iaoHTEIhRE7fgpdj G4fuxdYFYNB3AHZ2gyo8buJlevTrYmF09YA1Pl0Cb64eu5/OHnnTlz4Dmt1ePVtSowAr LZxozGH1jUSUGNCkq6zdsGZMeZQ9/B5KtDMi0rD/RZs5E7VBoy38WknRjfEI1k2b7atP Zk3A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=f1D2+AEArEhW2gM4q5VlXsUVZD8mb0A6TWokPtbcWJc=; b=oUhbIju0Xwsrau5kPv47nCzcqeL08r7PFg+zxnNMp6YmZxmw9X0tne9POhC+67Y9uD Jf7Vxa7EYCRb+cLg02mZuCGPH6UeOXqJk6WySjLHSFabgGi8k+JjIauutBZB97ABSs8u JL+casv5WghYDRQApgIgrlLjRfXj/x51/KTj+ceoBkiR1pezkP98hdI+ZNkKOVTSE1zQ erAJv/pYl+51ky7B/FqQr1wDErU5j/EEXW/TxLkpdAF9bZpZtUBuGj2SNkKpS06WVHG6 uBzh38l48Er/KkpJJ1Fj1xLj6Cv+Fihab2Pu4yGSF1OePV27r9BSK+AT08g91sJELMu1 R8lQ== X-Gm-Message-State: AOAM531U/VHdE0Jzdqrjxs80lU6JupCCC1WhGhsfY93/yQOcwbPhGL8u nylBpvdrgMNLoZzhhrVoKP0R/QR5/ffdXQ== X-Google-Smtp-Source: ABdhPJzLrfL7TFXRSwePc1X4wm876WNqu7/qT3Qk8C/8LF4yCBw0M61JOFDicpaMvQ0sPKpQAPRqZg== X-Received: by 2002:a63:ff51:: with SMTP id s17mr36213569pgk.415.1629798704150; Tue, 24 Aug 2021 02:51:44 -0700 (PDT) Received: from localhost.localdomain (125x103x255x1.ap125.ftth.ucom.ne.jp. [125.103.255.1]) by smtp.gmail.com with ESMTPSA id l19sm1873881pjq.10.2021.08.24.02.51.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 02:51:43 -0700 (PDT) From: QI Fuli X-Google-Original-From: QI Fuli To: nvdimm@lists.linux.dev Cc: QI Fuli Subject: [ndctl PATCH v2 5/5] ndctl, monitor: refator monitor for supporting multiple config files Date: Tue, 24 Aug 2021 18:51:06 +0900 Message-Id: <20210824095106.104808-6-qi.fuli@fujitsu.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210824095106.104808-1-qi.fuli@fujitsu.com> References: <20210824095106.104808-1-qi.fuli@fujitsu.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: QI Fuli Refactor ndctl monitor by using parse-configs helper to support multiple configuration files. Signed-off-by: QI Fuli --- Documentation/ndctl/Makefile.am | 2 +- Documentation/ndctl/ndctl-monitor.txt | 8 ++-- ndctl/monitor.c | 69 ++++++++++++++------------- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/Documentation/ndctl/Makefile.am b/Documentation/ndctl/Makefile.am index f0d5b21..37855cc 100644 --- a/Documentation/ndctl/Makefile.am +++ b/Documentation/ndctl/Makefile.am @@ -59,7 +59,7 @@ CLEANFILES = $(man1_MANS) .ONESHELL: attrs.adoc: $(srcdir)/Makefile.am $(AM_V_GEN) cat <<- EOF >$@ - :ndctl_monitorconfdir: $(ndctl_monitorconfdir) + :ndctl_confdir: $(ndctl_confdir) :ndctl_monitorconf: $(ndctl_monitorconf) :ndctl_keysdir: $(ndctl_keysdir) EOF diff --git a/Documentation/ndctl/ndctl-monitor.txt b/Documentation/ndctl/ndctl-monitor.txt index dbc9070..8c8c35b 100644 --- a/Documentation/ndctl/ndctl-monitor.txt +++ b/Documentation/ndctl/ndctl-monitor.txt @@ -21,8 +21,8 @@ objects and dumping the json format notifications to syslog, standard output or a logfile. The objects to monitor and smart events to notify can be selected by -setting options and/or the configuration file at -{ndctl_monitorconfdir}/{ndctl_monitorconf} +setting options and/or configuration files with .conf suffix under +{ndctl_confdir} Both, the values in configuration file and in options will work. If there is a conflict, the values in options will override the values in @@ -81,8 +81,8 @@ will not work if "--daemon" is specified. -c:: --config-file=:: - Provide the config file to use. This overrides the default config - typically found in {ndctl_monitorconfdir} + Provide the config file(s) to use. This overrides the default config + typically found in {ndctl_confdir} --daemon:: Run a monitor as a daemon. diff --git a/ndctl/monitor.c b/ndctl/monitor.c index ca36179..e944c90 100644 --- a/ndctl/monitor.c +++ b/ndctl/monitor.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,7 @@ static struct monitor { const char *log; - const char *config_file; + const char *configs; const char *dimm_event; FILE *log_file; bool daemon; @@ -463,7 +464,7 @@ out: return rc; } -static void parse_config(const char **arg, char *key, char *val, char *ident) +static void set_monitor_conf(const char **arg, char *key, char *val, char *ident) { struct strbuf value = STRBUF_INIT; size_t arg_len = *arg ? strlen(*arg) : 0; @@ -479,39 +480,25 @@ static void parse_config(const char **arg, char *key, char *val, char *ident) *arg = strbuf_detach(&value, NULL); } -static int read_config_file(struct ndctl_ctx *ctx, struct monitor *_monitor, - struct util_filter_params *_param) +static int parse_monitor_config(const struct config *configs, + const char *config_file) { FILE *f; size_t len = 0; int line = 0, rc = 0; - char *buf = NULL, *seek, *value, *config_file; - - if (_monitor->config_file) - config_file = strdup(_monitor->config_file); - else - config_file = strdup(NDCTL_CONF_FILE); - if (!config_file) { - fail("strdup default config file failed\n"); - rc = -ENOMEM; - goto out; - } + char *buf = NULL, *seek, *value; buf = malloc(BUF_SIZE); if (!buf) { fail("malloc read config-file buf error\n"); - rc = -ENOMEM; - goto out; + return -ENOMEM; } seek = buf; f = fopen(config_file, "r"); if (!f) { - if (_monitor->config_file) { - err(&monitor, "config-file: %s cannot be opened\n", - config_file); - rc = -errno; - } + err(&monitor, "%s cannot be opened\n", config_file); + rc = -errno; goto out; } @@ -554,19 +541,18 @@ static int read_config_file(struct ndctl_ctx *ctx, struct monitor *_monitor, if (len == 0) continue; - parse_config(&_param->bus, "bus", value, seek); - parse_config(&_param->dimm, "dimm", value, seek); - parse_config(&_param->region, "region", value, seek); - parse_config(&_param->namespace, "namespace", value, seek); - parse_config(&_monitor->dimm_event, "dimm-event", value, seek); + set_monitor_conf(¶m.bus, "bus", value, seek); + set_monitor_conf(¶m.dimm, "dimm", value, seek); + set_monitor_conf(¶m.region, "region", value, seek); + set_monitor_conf(¶m.namespace, "namespace", value, seek); + set_monitor_conf(&monitor.dimm_event, "dimm-event", value, seek); - if (!_monitor->log) - parse_config(&_monitor->log, "log", value, seek); + if (!monitor.log) + set_monitor_conf(&monitor.log, "log", value, seek); } fclose(f); out: free(buf); - free(config_file); return rc; } @@ -585,8 +571,8 @@ int cmd_monitor(int argc, const char **argv, struct ndctl_ctx *ctx) OPT_FILENAME('l', "log", &monitor.log, " | syslog | standard", "where to output the monitor's notification"), - OPT_FILENAME('c', "config-file", &monitor.config_file, - "config-file", "override the default config"), + OPT_STRING('c', "config-file", &monitor.configs, + "config-file", "override default configs"), OPT_BOOLEAN('\0', "daemon", &monitor.daemon, "run ndctl monitor as a daemon"), OPT_BOOLEAN('u', "human", &monitor.human, @@ -601,6 +587,19 @@ int cmd_monitor(int argc, const char **argv, struct ndctl_ctx *ctx) "ndctl monitor []", NULL }; + const struct config configs[] = { + CONF_MONITOR(NDCTL_CONF_FILE, parse_monitor_config), + CONF_STR("core:bus", ¶m.bus, NULL), + CONF_STR("core:region", ¶m.region, NULL), + CONF_STR("core:dimm", ¶m.dimm, NULL), + CONF_STR("core:namespace", ¶m.namespace, NULL), + CONF_STR("monitor:bus", ¶m.bus, NULL), + CONF_STR("monitor:region", ¶m.region, NULL), + CONF_STR("monitor:dimm", ¶m.dimm, NULL), + CONF_STR("monitor:namespace", ¶m.namespace, NULL), + CONF_STR("monitor:dimm-event", &monitor.dimm_event, NULL), + CONF_END(), + }; const char *prefix = "./"; struct util_filter_ctx fctx = { 0 }; struct monitor_filter_arg mfa = { 0 }; @@ -621,7 +620,11 @@ int cmd_monitor(int argc, const char **argv, struct ndctl_ctx *ctx) else monitor.ctx.log_priority = LOG_INFO; - rc = read_config_file(ctx, &monitor, ¶m); + if (monitor.configs) + rc = parse_configs_prefix(monitor.configs, prefix, configs); + else + rc = parse_configs_prefix( + ndctl_get_configs(ctx), prefix, configs); if (rc) goto out;