From patchwork Mon Dec 6 22:28:19 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659967 Received: from mga04.intel.com (mga04.intel.com [192.55.52.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 98D0E2CA6 for ; Mon, 6 Dec 2021 22:29:02 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="236159413" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="236159413" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by fmsmga104.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:41 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310415" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:40 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, QI Fuli , Vishal Verma Subject: [ndctl PATCH v2 01/12] ndctl, util: add iniparser helper Date: Mon, 6 Dec 2021 15:28:19 -0700 Message-Id: <20211206222830.2266018-2-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=63806; i=vishal.l.verma@intel.com; h=from:subject; bh=OuIPresmws/xx9zoiHunkfC+h5PmCYYLHpA3Q/qd4YE=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+rp/l4sePK0rdM958ZGbItmFG2IPPNjXs13pcvUs6d+v dCRvdpSyMIhxMciKKbL83fOR8Zjc9nyewARHmDmsTCBDGLg4BWAinG8Z/tff+v6Hu+TxLcfESTFT05 5UvMyTq7ZPZjV7ffvPxKR8o3JGhjcJ91wYjm45IeMg+9S9jpHpkOnnldHvlsec473iazV7BysA X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF From: QI Fuli Add iniparser[1] helper for parsing ndctl global configuration files. [1] https://github.com/ndevilla/iniparser Link: https://lore.kernel.org/r/20210824095106.104808-2-qi.fuli@fujitsu.com Signed-off-by: QI Fuli Signed-off-by: Vishal Verma --- Makefile.am | 6 +- util/dictionary.h | 175 ++++++++++ util/iniparser.h | 360 ++++++++++++++++++++ util/dictionary.c | 383 +++++++++++++++++++++ util/iniparser.c | 838 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1761 insertions(+), 1 deletion(-) create mode 100644 util/dictionary.h create mode 100644 util/iniparser.h create mode 100644 util/dictionary.c create mode 100644 util/iniparser.c 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.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.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 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/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); +} From patchwork Mon Dec 6 22:28:20 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659963 Received: from mga04.intel.com (mga04.intel.com [192.55.52.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9D6E92CB3 for ; Mon, 6 Dec 2021 22:29:02 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="236159418" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="236159418" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by fmsmga104.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:41 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310419" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:41 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, QI Fuli , Vishal Verma Subject: [ndctl PATCH v2 02/12] ndctl, util: add parse-configs helper Date: Mon, 6 Dec 2021 15:28:20 -0700 Message-Id: <20211206222830.2266018-3-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=4096; i=vishal.l.verma@intel.com; h=from:subject; bh=/FPon6BgNSScFjsTiy9JG4E8k2vQqVLptr5vS0B4ajM=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+nqU/whPzFrPPCVG+dj1SxwNG5d9jZqjvGb+9dIvdzlM wsM6OkpZGMS4GGTFFFn+7vnIeExuez5PYIIjzBxWJpAhDFycAjCRnLUM/z28RNYGvFZqk51gzXZU5N rRBS8ZU9laAhzSr9475NIhbM/wT8VQ88j7eVN9Dnx685P/xh/PJWtsp1jcfie/TurUfmn9ElYA X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF From: QI Fuli Add parse-config util to help ndctl commands parse ndctl global configuration files. Link: https://lore.kernel.org/r/20210824095106.104808-3-qi.fuli@fujitsu.com Signed-off-by: QI Fuli Signed-off-by: Vishal Verma --- Makefile.am | 2 ++ util/parse-configs.h | 34 ++++++++++++++++++ util/parse-configs.c | 82 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 util/parse-configs.h create mode 100644 util/parse-configs.c 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.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); 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; + +} From patchwork Mon Dec 6 22:28:21 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659965 Received: from mga04.intel.com (mga04.intel.com [192.55.52.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EE7ED6D19 for ; Mon, 6 Dec 2021 22:29:03 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="236159420" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="236159420" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by fmsmga104.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:43 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310426" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:41 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, QI Fuli , Vishal Verma Subject: [ndctl PATCH v2 03/12] ndctl: make ndctl support configuration files Date: Mon, 6 Dec 2021 15:28:21 -0700 Message-Id: <20211206222830.2266018-4-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=6278; i=vishal.l.verma@intel.com; h=from:subject; bh=OBjNslSnfV5IPEDTDiyymiGzda9gO91BNgvDtRnu7+4=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+no49XNXb5le7eZ8ULU4YsaXiQ7vNLc9FD9iKP1ovesJ pYUTOkpZGMS4GGTFFFn+7vnIeExuez5PYIIjzBxWJpAhDFycAjCRu/MY/vCUpEqZzn52655lctPmay bzr2qsniTA4BKr/Nz7K8u+dG+G/84fv3/3z+BcPDsgt+9x8osQiyTP6c8Sn69ov3dqy5Ecax4A X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF 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. Link: https://lore.kernel.org/r/20210824095106.104808-4-qi.fuli@fujitsu.com Signed-off-by: QI Fuli Signed-off-by: Vishal Verma --- configure.ac | 4 ++-- ndctl/lib/private.h | 1 + ndctl/lib/libndctl.c | 54 ++++++++++++++++++++++++++++++++++++++++++ ndctl/libndctl.h | 2 ++ ndctl/ndctl.c | 1 + ndctl/Makefile.am | 5 ++-- ndctl/lib/Makefile.am | 4 ++++ ndctl/lib/libndctl.sym | 2 ++ 8 files changed, 69 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/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/lib/libndctl.c b/ndctl/lib/libndctl.c index 536e142..6b68e3a 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,58 @@ 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_dir(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) { + if (errno != ENOENT) + err(*ctx, "scandir for configs failed: %s\n", + strerror(errno)); + 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_dir(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 +384,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/libndctl.h b/ndctl/libndctl.h index 87d07b7..883a56f 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_dir(struct ndctl_ctx **ctx, char *conf_dir); +const char *ndctl_get_configs_dir(struct ndctl_ctx *ctx); enum ndctl_persistence_domain { PERSISTENCE_NONE = 0, diff --git a/ndctl/ndctl.c b/ndctl/ndctl.c index 31d2c5e..0f908db 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_dir(&ctx, NDCTL_CONF_DIR); main_handle_internal_command(argc, argv, ctx, commands, ARRAY_SIZE(commands), PROG_NDCTL); ndctl_unref(ctx); 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.sym b/ndctl/lib/libndctl.sym index 58afb74..6e1b510 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_dir; + ndctl_get_configs_dir; } LIBNDCTL_25; From patchwork Mon Dec 6 22:28:22 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659945 Received: from mga01.intel.com (mga01.intel.com [192.55.52.88]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 029F62CA8 for ; Mon, 6 Dec 2021 22:28:43 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="261484506" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="261484506" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by fmsmga101.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:43 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310431" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:42 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, QI Fuli , Vishal Verma Subject: [ndctl PATCH v2 04/12] ndctl, config: add the default ndctl configuration file Date: Mon, 6 Dec 2021 15:28:22 -0700 Message-Id: <20211206222830.2266018-5-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=4289; i=vishal.l.verma@intel.com; h=from:subject; bh=i5SzAjuKS7AdOmKikpy/Xy3eB+AxbULX34tPxzRIvI0=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+nr27glS7+avmnIgpqedhT8uysCVp1AsatN58Y8urpde rfXuKGVhEONikBVTZPm75yPjMbnt+TyBCY4wc1iZQIYwcHEKwEQ+PGH4KyD//HqFAcdMxUnaOSfEDm 2cGH3N5uFCyQCTS92BH9abVDH89/3R0zljSqjoncAA8fY9O78sy+wVe7Bw/dxM7bbN0S3zWAA= X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF From: QI Fuli Install ndctl/ndctl.conf as the default ndctl configuration file. Link: https://lore.kernel.org/r/20210824095106.104808-5-qi.fuli@fujitsu.com Signed-off-by: QI Fuli Signed-off-by: Vishal Verma Reviewed-by: Dan Williams --- 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 Mon Dec 6 22:28:23 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659943 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CD77F2CA6 for ; Mon, 6 Dec 2021 22:28:43 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="300804830" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="300804830" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:43 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310438" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:42 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, QI Fuli , Vishal Verma Subject: [ndctl PATCH v2 05/12] ndctl, monitor: refator monitor for supporting multiple config files Date: Mon, 6 Dec 2021 15:28:23 -0700 Message-Id: <20211206222830.2266018-6-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=7030; i=vishal.l.verma@intel.com; h=from:subject; bh=0FNwwOkFG0eXJo4s9p4HFwxBhDNBFB7cH2qRCHR5rwc=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+nq2sWZFH6vjfP59blToriDJQ9u42YvWnZzkb/7DceVb m2K/jlIWBjEuBlkxRZa/ez4yHpPbns8TmOAIM4eVCWQIAxenAEzkhgEjwyMT18Bgvrqsc52WCzasex qeKFA0c/pJnmVCdloN3psM1Bj+FwrUh0/e7vLI3D9NxPPH7X+VbJ+c57311A8OOi5XotTMDQA= X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF From: QI Fuli Refactor ndctl monitor by using parse-configs helper to support multiple configuration files. Link: https://lore.kernel.org/r/20210824095106.104808-6-qi.fuli@fujitsu.com Signed-off-by: QI Fuli Signed-off-by: Vishal Verma Reviewed-by: Dan Williams --- Documentation/ndctl/ndctl-monitor.txt | 8 +-- ndctl/monitor.c | 73 ++++++++++++++------------- Documentation/ndctl/Makefile.am | 2 +- 3 files changed, 44 insertions(+), 39 deletions(-) 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..6bf3160 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,7 +587,20 @@ int cmd_monitor(int argc, const char **argv, struct ndctl_ctx *ctx) "ndctl monitor []", NULL }; - const char *prefix = "./"; + 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 = "./", *ndctl_configs; struct util_filter_ctx fctx = { 0 }; struct monitor_filter_arg mfa = { 0 }; int i, rc; @@ -621,7 +620,13 @@ 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); + ndctl_configs = ndctl_get_configs_dir(ctx); + if (monitor.configs) + rc = parse_configs_prefix(monitor.configs, prefix, configs); + else if (ndctl_configs) + rc = parse_configs_prefix(ndctl_configs, prefix, configs); + else + rc = 0; if (rc) goto out; 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 From patchwork Mon Dec 6 22:28:24 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659947 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EE1252CAB for ; Mon, 6 Dec 2021 22:28:44 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="300804831" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="300804831" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:43 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310441" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:43 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, Vishal Verma , QI Fuli Subject: [ndctl PATCH v2 06/12] ndctl: Update ndctl.spec.in for 'ndctl.conf' Date: Mon, 6 Dec 2021 15:28:24 -0700 Message-Id: <20211206222830.2266018-7-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=728; h=from:subject; bh=10cHrrDXZDVIoR8oSMifEvozAt3pTpAZaVY0KbkOEqs=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+no7Q3gnBWnzsvBNPnCmaZNH1z+v/86Np9ZMnao6Ud7h 14WijlIWBjEuBlkxRZa/ez4yHpPbns8TmOAIM4eVCWQIAxenAEzkmwwjQ7NYyZFfTcvO8/nu4ehp3Z fq8XXvqf7tvdrJ+i0VXSv/6DEy3Aw5FDVXVszf7OUcjSvG/YYCf2Zf+tk598Yn6e+br6Zk8QAA X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF The new config system introduces and installs a sample config file called ndctl.conf. Update the RPM spec to include this in the %files section for ndctl. Cc: QI Fuli Reviewed-by: QI Fuli Signed-off-by: Vishal Verma Reviewed-by: Dan Williams --- ndctl.spec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/ndctl.spec.in b/ndctl.spec.in index 0563b2d..07c36ec 100644 --- a/ndctl.spec.in +++ b/ndctl.spec.in @@ -118,6 +118,7 @@ make check %{_sysconfdir}/modprobe.d/nvdimm-security.conf %config(noreplace) %{_sysconfdir}/ndctl/monitor.conf +%config(noreplace) %{_sysconfdir}/ndctl/ndctl.conf %files -n daxctl %defattr(-,root,root) From patchwork Mon Dec 6 22:28:25 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659949 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B7BE02CAD for ; Mon, 6 Dec 2021 22:28:45 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="300804834" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="300804834" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:44 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310447" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:43 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, Vishal Verma Subject: [ndctl PATCH v2 07/12] daxctl: Documentation updates for persistent reconfiguration Date: Mon, 6 Dec 2021 15:28:25 -0700 Message-Id: <20211206222830.2266018-8-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=3601; h=from:subject; bh=wQoZjseJQDfY4ShW0ZkiZc1gRzpVXqr9aYv6ubql5Cs=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+nrjWfpSg+c2220+Grs29ugjT4WD3J/5HVZbdRt8j+pP apXqKGVhEONikBVTZPm75yPjMbnt+TyBCY4wc1iZQIYwcHEKwESaAhj+GUUzrusRP+IZMsmueppA5L fX2+Ri2Ji+binp13wf2CXJzPDfp8Nm852/ybFLCuy0ki4IVp7ifyWRUnN087ouw8xY/ZsMAA== X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF Add a man page update describing how daxctl-reconfigure-device(1) can be used for persistent reconfiguration of a daxctl device using a config file. Cc: Dan Williams Signed-off-by: Vishal Verma Reviewed-by: Dan Williams --- .../daxctl/daxctl-reconfigure-device.txt | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/Documentation/daxctl/daxctl-reconfigure-device.txt b/Documentation/daxctl/daxctl-reconfigure-device.txt index f112b3c..aa87d45 100644 --- a/Documentation/daxctl/daxctl-reconfigure-device.txt +++ b/Documentation/daxctl/daxctl-reconfigure-device.txt @@ -162,6 +162,15 @@ include::region-option.txt[] brought online automatically and immediately with the 'online_movable' policy. Use this option to disable the automatic onlining behavior. +-C:: +--check-config:: + Get reconfiguration parameters from the global daxctl config file. + This is typically used when daxctl-reconfigure-device is called from + a systemd-udevd device unit file. The reconfiguration proceeds only + if the match parameters in a 'reconfigure-device' section of the + config match the dax device specified on the command line. See the + 'PERSISTENT RECONFIGURATION' section for more details. + include::movable-options.txt[] -f:: @@ -183,6 +192,64 @@ include::human-option.txt[] include::verbose-option.txt[] +PERSISTENT RECONFIGURATION +-------------------------- + +The 'mode' of a daxctl device is not persistent across reboots by default. This +is because the device itself does not hold any metadata that hints at what mode +it was set to, or is intended to be used. The default mode for such a device +on boot is 'devdax'. + +The administrator may set policy such that certain dax devices are always +reconfigured into a target configuration every boot. This is accomplished via a +daxctl config file. + +The config file may have multiple sections influencing different aspects of +daxctl operation. The section of interest for persistent reconfiguration is +'reconfigure-device'. The format of this is as follows: + +---- +[reconfigure-device ] +nvdimm.uuid = +mode = (default: system-ram) +online = (default: true) +movable = (default: true) +---- + +Here is an example of a config snippet for managing three devdax namespaces, +one is left in devdax mode, the second is changed to system-ram mode with +default options (online, movable), and the third is set to system-ram mode, +the memory is onlined, but not movable. + +Note that the 'subsection name' can be arbitrary, and is only used to +identify a specific config section. It does not have to match the 'device +name' (e.g. 'dax0.0' etc). + +---- +[reconfigure-device dax0] +nvdimm.uuid = ed93e918-e165-49d8-921d-383d7b9660c5 +mode = devdax + +[reconfigure-device dax1] +nvdimm.uuid = f36d02ff-1d9f-4fb9-a5b9-8ceb10a00fe3 +mode = system-ram + +[reconfigure-device dax2] +nvdimm.uuid = f36d02ff-1d9f-4fb9-a5b9-8ceb10a00fe3 +mode = system-ram +online = true +movable = false +---- + +The following example can be used to create a devdax mode namespace, and +simultaneously add the newly created namespace to the config file for +system-ram conversion. + +---- +ndctl create-namespace --mode=devdax | \ + jq -r "\"[reconfigure-device $(uuidgen)]\", \"nvdimm.uuid = \(.uuid)\", \"mode = system-ram\"" >> $config_path +---- + include::../copyright.txt[] SEE ALSO From patchwork Mon Dec 6 22:28:26 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659951 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8ACB12CA8 for ; Mon, 6 Dec 2021 22:28:46 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="300804836" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="300804836" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:44 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310453" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:44 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, Vishal Verma , QI Fuli Subject: [ndctl PATCH v2 08/12] util/parse-config: refactor filter_conf_files into util/ Date: Mon, 6 Dec 2021 15:28:26 -0700 Message-Id: <20211206222830.2266018-9-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=3826; h=from:subject; bh=C0y7/la9iW9jVx5PaYAhfAzzGUlBfTcj6WPDlMLwvEI=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+noZRHqem/1cPE/hsTSzhFzCb9+1Pkzyz/SfN6Qx1Vc7 7FzYUcrCIMbFICumyPJ3z0fGY3Lb83kCExxh5rAygQ3h4hSAiQhGMTIsnlQ2+8rS+9c//Ziczi72t6 uW48i5w65u2///SWXfIzjtLsP/HKGXXetEBfSuzvLK/Bfa23DyxtX8E7KBb114b4WJz3ZnAgA= X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF Move filter_conf() into util/parse-configs.c as filter_conf_files() so that it can be reused by the config parser in daxctl. Cc: QI Fuli Reviewed-by: QI Fuli Signed-off-by: Vishal Verma Reviewed-by: Dan Williams --- ndctl/lib/libndctl.c | 19 ++----------------- util/parse-configs.h | 4 ++++ util/parse-configs.c | 17 +++++++++++++++++ daxctl/lib/Makefile.am | 2 ++ ndctl/lib/Makefile.am | 2 ++ 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c index 6b68e3a..9d33005 100644 --- a/ndctl/lib/libndctl.c +++ b/ndctl/lib/libndctl.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -266,22 +267,6 @@ 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_dir(struct ndctl_ctx **ctx, char *conf_dir) { struct dirent **namelist; @@ -291,7 +276,7 @@ NDCTL_EXPORT void ndctl_set_configs_dir(struct ndctl_ctx **ctx, char *conf_dir) if ((!ctx) || (!conf_dir)) return; - rc = scandir(conf_dir, &namelist, filter_conf, alphasort); + rc = scandir(conf_dir, &namelist, filter_conf_files, alphasort); if (rc == -1) { if (errno != ENOENT) err(*ctx, "scandir for configs failed: %s\n", diff --git a/util/parse-configs.h b/util/parse-configs.h index f70f58f..491aebb 100644 --- a/util/parse-configs.h +++ b/util/parse-configs.h @@ -1,8 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2021, FUJITSU LIMITED. ALL rights reserved. +#include #include #include +#include #include enum parse_conf_type { @@ -11,6 +13,8 @@ enum parse_conf_type { MONITOR_CALLBACK, }; +int filter_conf_files(const struct dirent *dir); + struct config; typedef int parse_conf_cb(const struct config *, const char *config_file); diff --git a/util/parse-configs.c b/util/parse-configs.c index 44dcff4..aac129b 100644 --- a/util/parse-configs.c +++ b/util/parse-configs.c @@ -6,6 +6,23 @@ #include #include +int filter_conf_files(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; +} + static void set_str_val(const char **value, const char *val) { struct strbuf buf = STRBUF_INIT; diff --git a/daxctl/lib/Makefile.am b/daxctl/lib/Makefile.am index 25efd83..db2351e 100644 --- a/daxctl/lib/Makefile.am +++ b/daxctl/lib/Makefile.am @@ -15,6 +15,8 @@ libdaxctl_la_SOURCES =\ ../../util/sysfs.h \ ../../util/log.c \ ../../util/log.h \ + ../../util/parse-configs.h \ + ../../util/parse-configs.c \ libdaxctl.c libdaxctl_la_LIBADD =\ diff --git a/ndctl/lib/Makefile.am b/ndctl/lib/Makefile.am index f741c44..8020eb4 100644 --- a/ndctl/lib/Makefile.am +++ b/ndctl/lib/Makefile.am @@ -19,6 +19,8 @@ libndctl_la_SOURCES =\ ../../util/wrapper.c \ ../../util/usage.c \ ../../util/fletcher.h \ + ../../util/parse-configs.h \ + ../../util/parse-configs.c \ dimm.c \ inject.c \ nfit.c \ From patchwork Mon Dec 6 22:28:27 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659955 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E947C2CB2 for ; Mon, 6 Dec 2021 22:28:46 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="300804837" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="300804837" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:45 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310457" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:44 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, Vishal Verma , QI Fuli Subject: [ndctl PATCH v2 09/12] daxctl: add basic config parsing support Date: Mon, 6 Dec 2021 15:28:27 -0700 Message-Id: <20211206222830.2266018-10-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=6418; h=from:subject; bh=TY59o3SbW7My8IE9KZ1oV6icSPungbvlaVrYC3TgWhY=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+no1XoX0WBfPn7Hv8NRD1qGBlzetCY41m6Olnnaw7+nJ N1afOkpZGMS4GGTFFFn+7vnIeExuez5PYIIjzBxWJpAhDFycAjARoTeMDOvOrPk/VeD6unMsy72sTh namOr6rP1qLbDw7bWn05NVTeQZGX43KG5nOHL7E8OxqGl/+AT+cZndE3Dy7tny/RHn4dbo7VwA X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF Add support similar to ndctl and libndctl for parsing config files. This allows storing a config file path/list in the daxctl_ctx, and adds APIs for setting and retrieving it. Cc: QI Fuli Reviewed-by: QI Fuli Signed-off-by: Vishal Verma --- .../daxctl/daxctl-reconfigure-device.txt | 8 ++++ configure.ac | 3 ++ daxctl/lib/libdaxctl.c | 39 +++++++++++++++++++ daxctl/libdaxctl.h | 2 + Documentation/daxctl/Makefile.am | 11 +++++- daxctl/Makefile.am | 1 + daxctl/lib/Makefile.am | 4 ++ daxctl/lib/libdaxctl.sym | 2 + 8 files changed, 69 insertions(+), 1 deletion(-) diff --git a/Documentation/daxctl/daxctl-reconfigure-device.txt b/Documentation/daxctl/daxctl-reconfigure-device.txt index aa87d45..09556cc 100644 --- a/Documentation/daxctl/daxctl-reconfigure-device.txt +++ b/Documentation/daxctl/daxctl-reconfigure-device.txt @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 +include::attrs.adoc[] + daxctl-reconfigure-device(1) ============================ @@ -250,6 +252,12 @@ ndctl create-namespace --mode=devdax | \ jq -r "\"[reconfigure-device $(uuidgen)]\", \"nvdimm.uuid = \(.uuid)\", \"mode = system-ram\"" >> $config_path ---- +The default location for daxctl config files is under {daxctl_confdir}/, +and any file with a '.conf' suffix at this location is considered. It is +acceptable to have multiple files containing ini-style config sections, +but the {section, subsection} tuple must be unique across all config files +under {daxctl_confdir}/. + include::../copyright.txt[] SEE ALSO diff --git a/configure.ac b/configure.ac index 9e1c6db..e779b51 100644 --- a/configure.ac +++ b/configure.ac @@ -178,6 +178,9 @@ AC_SUBST([ndctl_confdir]) AC_SUBST([ndctl_conf]) AC_SUBST([ndctl_monitorconf]) +daxctl_confdir=${sysconfdir}/daxctl +AC_SUBST([daxctl_confdir]) + daxctl_modprobe_datadir=${datadir}/daxctl daxctl_modprobe_data=daxctl.conf AC_SUBST([daxctl_modprobe_datadir]) diff --git a/daxctl/lib/libdaxctl.c b/daxctl/lib/libdaxctl.c index 860bd9c..2de3616 100644 --- a/daxctl/lib/libdaxctl.c +++ b/daxctl/lib/libdaxctl.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include "libdaxctl-private.h" @@ -37,6 +39,7 @@ struct daxctl_ctx { struct log_ctx ctx; int refcount; void *userdata; + const char *configs; int regions_init; struct list_head regions; struct kmod_ctx *kmod_ctx; @@ -68,6 +71,42 @@ DAXCTL_EXPORT void daxctl_set_userdata(struct daxctl_ctx *ctx, void *userdata) ctx->userdata = userdata; } +DAXCTL_EXPORT void daxctl_set_configs_dir(struct daxctl_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_files, alphasort); + if (rc == -1) { + if (errno != ENOENT) + err(*ctx, "scandir for configs failed: %s\n", + strerror(errno)); + 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); +} + +DAXCTL_EXPORT const char *daxctl_get_configs_dir(struct daxctl_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + return ctx->configs; +} + /** * daxctl_new - instantiate a new library context * @ctx: context to establish diff --git a/daxctl/libdaxctl.h b/daxctl/libdaxctl.h index 683ae9c..6be09d9 100644 --- a/daxctl/libdaxctl.h +++ b/daxctl/libdaxctl.h @@ -28,6 +28,8 @@ int daxctl_get_log_priority(struct daxctl_ctx *ctx); void daxctl_set_log_priority(struct daxctl_ctx *ctx, int priority); void daxctl_set_userdata(struct daxctl_ctx *ctx, void *userdata); void *daxctl_get_userdata(struct daxctl_ctx *ctx); +void daxctl_set_configs_dir(struct daxctl_ctx **ctx, char *conf_dir); +const char *daxctl_get_configs_dir(struct daxctl_ctx *ctx); struct daxctl_region; struct daxctl_region *daxctl_new_region(struct daxctl_ctx *ctx, int id, diff --git a/Documentation/daxctl/Makefile.am b/Documentation/daxctl/Makefile.am index 5991731..9c43e61 100644 --- a/Documentation/daxctl/Makefile.am +++ b/Documentation/daxctl/Makefile.am @@ -33,11 +33,20 @@ EXTRA_DIST = $(man1_MANS) CLEANFILES = $(man1_MANS) +.ONESHELL: +attrs.adoc: $(srcdir)/Makefile.am + $(AM_V_GEN) cat <<- EOF >$@ + :daxctl_confdir: $(daxctl_confdir) + :daxctl_conf: $(daxctl_conf) + :ndctl_keysdir: $(ndctl_keysdir) + EOF + XML_DEPS = \ ../../version.m4 \ ../copyright.txt \ Makefile \ - $(CONFFILE) + $(CONFFILE) \ + attrs.adoc RM ?= rm -f diff --git a/daxctl/Makefile.am b/daxctl/Makefile.am index 9b1313a..a5c6de1 100644 --- a/daxctl/Makefile.am +++ b/daxctl/Makefile.am @@ -10,6 +10,7 @@ config.h: $(srcdir)/Makefile.am "$(daxctl_modprobe_datadir)/$(daxctl_modprobe_data)"' >>$@ && \ echo '#define DAXCTL_MODPROBE_INSTALL \ "$(sysconfdir)/modprobe.d/$(daxctl_modprobe_data)"' >>$@ + $(AM_V_GEN) echo '#define DAXCTL_CONF_DIR "$(daxctl_confdir)"' >>$@ daxctl_SOURCES =\ daxctl.c \ diff --git a/daxctl/lib/Makefile.am b/daxctl/lib/Makefile.am index db2351e..7a53598 100644 --- a/daxctl/lib/Makefile.am +++ b/daxctl/lib/Makefile.am @@ -13,6 +13,10 @@ libdaxctl_la_SOURCES =\ ../../util/iomem.h \ ../../util/sysfs.c \ ../../util/sysfs.h \ + ../../util/strbuf.h \ + ../../util/strbuf.c \ + ../../util/wrapper.c \ + ../../util/usage.c \ ../../util/log.c \ ../../util/log.h \ ../../util/parse-configs.h \ diff --git a/daxctl/lib/libdaxctl.sym b/daxctl/lib/libdaxctl.sym index a13e93d..7098aba 100644 --- a/daxctl/lib/libdaxctl.sym +++ b/daxctl/lib/libdaxctl.sym @@ -96,4 +96,6 @@ LIBDAXCTL_9 { global: daxctl_dev_will_auto_online_memory; daxctl_dev_has_online_memory; + daxctl_set_configs_dir; + daxctl_get_configs_dir; } LIBDAXCTL_8; From patchwork Mon Dec 6 22:28:28 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659953 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0BAE32CB6 for ; Mon, 6 Dec 2021 22:28:47 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="300804839" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="300804839" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:45 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310463" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:45 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, Vishal Verma , QI Fuli Subject: [ndctl PATCH v2 10/12] util/parse-configs: add a key/value search helper Date: Mon, 6 Dec 2021 15:28:28 -0700 Message-Id: <20211206222830.2266018-11-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=3874; h=from:subject; bh=+c9PzAL7gilmjcspgyDw3H/dUTOIz9Rw7aE+EZIpuRo=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+nrDf1R6p35uXvihu9vtbunDTZXHJXj0JI8bMHtEnrL4 uu9/RykLgxgXg6yYIsvfPR8Zj8ltz+cJTHCEmcPKBDKEgYtTAC5iycjQuaGPS09XSvB0WPmPssMXuL bfTPWdfTjwu5nAhbPRz0ytGf5pOnyJYpF1/WdV7Gpx6gm3YnLEvbBr/KLlKXFPGnMLHzMDAA== X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF Add a new config query type called CONFIG_SEARCH_SECTION, which searches all loaded config files based on a query criteria of: specified section name, specified key/value pair within that section, and can return other key/values from the section that matched the search criteria. This allows for multiple named subsections, where a subsection name is of the type: '[section subsection]'. Cc: QI Fuli Cc: Dan Williams Reviewed-by: QI Fuli Signed-off-by: Vishal Verma --- util/parse-configs.h | 15 +++++++++++++ util/parse-configs.c | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/util/parse-configs.h b/util/parse-configs.h index 491aebb..6dcc01c 100644 --- a/util/parse-configs.h +++ b/util/parse-configs.h @@ -9,6 +9,7 @@ enum parse_conf_type { CONFIG_STRING, + CONFIG_SEARCH_SECTION, CONFIG_END, MONITOR_CALLBACK, }; @@ -20,6 +21,10 @@ typedef int parse_conf_cb(const struct config *, const char *config_file); struct config { enum parse_conf_type type; + const char *section; + const char *search_key; + const char *search_val; + const char *get_key; const char *key; void *value; void *defval; @@ -31,6 +36,16 @@ struct config { #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_SEARCH(s, sk, sv, gk, v, d) \ +{ \ + .type = CONFIG_SEARCH_SECTION, \ + .section = (s), \ + .search_key = (sk), \ + .search_val = (sv), \ + .get_key = (gk), \ + .value = check_vtype(v, const char **), \ + .defval = (d) \ +} #define CONF_MONITOR(k,f) \ { .type = MONITOR_CALLBACK, .key = (k), .callback = (f)} diff --git a/util/parse-configs.c b/util/parse-configs.c index aac129b..ca1daef 100644 --- a/util/parse-configs.c +++ b/util/parse-configs.c @@ -39,6 +39,54 @@ static void set_str_val(const char **value, const char *val) *value = strbuf_detach(&buf, NULL); } +static const char *search_section_kv(dictionary *d, const struct config *c) +{ + int i; + + for (i = 0; i < iniparser_getnsec(d); i++) { + const char *cur_sec_full = iniparser_getsecname(d, i); + char *cur_sec = strdup(cur_sec_full); + const char *search_val, *ret_val; + const char *delim = " \t\n\r"; + char *save, *cur, *query; + + if (!cur_sec) + return NULL; + if (!c->section || !c->search_key || !c->search_val || !c->get_key) { + fprintf(stderr, "warning: malformed config query, skipping\n"); + return NULL; + } + + cur = strtok_r(cur_sec, delim, &save); + if ((cur == NULL) || (strcmp(cur, c->section) != 0)) + goto out_sec; + + if (asprintf(&query, "%s:%s", cur_sec_full, c->search_key) < 0) + goto out_sec; + search_val = iniparser_getstring(d, query, NULL); + if (!search_val) + goto out_query; + if (strcmp(search_val, c->search_val) != 0) + goto out_query; + + /* we're now in a matching section */ + free(query); + if (asprintf(&query, "%s:%s", cur_sec_full, c->get_key) < 0) + goto out_sec; + ret_val = iniparser_getstring(d, query, NULL); + free(query); + free(cur_sec); + return ret_val; + +out_query: + free(query); +out_sec: + free(cur_sec); + } + + return NULL; +} + static int parse_config_file(const char *config_file, const struct config *configs) { @@ -55,6 +103,9 @@ static int parse_config_file(const char *config_file, iniparser_getstring(dic, configs->key, configs->defval)); break; + case CONFIG_SEARCH_SECTION: + set_str_val((const char **)configs->value, + search_section_kv(dic, configs)); case MONITOR_CALLBACK: case CONFIG_END: break; From patchwork Mon Dec 6 22:28:29 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659957 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C07A72CB9 for ; Mon, 6 Dec 2021 22:28:47 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="300804842" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="300804842" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:46 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310467" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:45 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, Vishal Verma , QI Fuli Subject: [ndctl PATCH v2 11/12] daxctl/device.c: add an option for getting params from a config file Date: Mon, 6 Dec 2021 15:28:29 -0700 Message-Id: <20211206222830.2266018-12-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=8367; h=from:subject; bh=3b2qu3lmsUEaf0aRs+BtN0ZeNtvEclZjg+cBmGvc8F0=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+vrMl5Tyrb224XxTimByRuj2zLzP2xqNuM8LMptpdodu CNzWUcrCIMbFICumyPJ3z0fGY3Lb83kCExxh5rAygQxh4OIUgIk4rmL4n/13RoNUgP/5mcEyPiG+bS saN6+/sfbmIssVd1Pr4pT79RgZ7ty9osI3+3SD2f6iH2v2xra3/fnMbJf1n1/42hVW/UtneAE= X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF Add a new option to daxctl-reconfigure-device that allows it to comprehend the new global config system in ndctl/daxctl. With this, the reconfigure-device command can query the config to match a specific device UUID, and operate using the parameters supplied in that INI section. This is in preparation to make daxctl device reconfiguration (usually as system-ram) policy based, so that reconfiguration can happen automatically on boot. Cc: QI Fuli Cc: Dan Williams Signed-off-by: Vishal Verma --- daxctl/daxctl.c | 1 + daxctl/device.c | 174 ++++++++++++++++++++++++++++++++++++++++++++- daxctl/Makefile.am | 1 + 3 files changed, 174 insertions(+), 2 deletions(-) diff --git a/daxctl/daxctl.c b/daxctl/daxctl.c index 928814c..41b35e5 100644 --- a/daxctl/daxctl.c +++ b/daxctl/daxctl.c @@ -95,6 +95,7 @@ int main(int argc, const char **argv) rc = daxctl_new(&ctx); if (rc) goto out; + daxctl_set_configs_dir(&ctx, DAXCTL_CONF_DIR); main_handle_internal_command(argc, argv, ctx, commands, ARRAY_SIZE(commands), PROG_DAXCTL); daxctl_unref(ctx); diff --git a/daxctl/device.c b/daxctl/device.c index a427b7d..7b02420 100644 --- a/daxctl/device.c +++ b/daxctl/device.c @@ -14,8 +14,10 @@ #include #include #include +#include #include #include +#include #include static struct { @@ -25,6 +27,7 @@ static struct { const char *size; const char *align; const char *input; + bool check_config; bool no_online; bool no_movable; bool force; @@ -65,6 +68,9 @@ enum device_action { ACTION_DESTROY, }; +#define CONF_SECTION "reconfigure-device" +#define CONF_NVDIMM_UUID_STR "nvdimm.uuid" + #define BASE_OPTIONS() \ OPT_STRING('r', "region", ¶m.region, "region-id", "filter by region"), \ OPT_BOOLEAN('u', "human", ¶m.human, "use human friendly number formats"), \ @@ -75,7 +81,9 @@ OPT_STRING('m', "mode", ¶m.mode, "mode", "mode to switch the device to"), \ OPT_BOOLEAN('N', "no-online", ¶m.no_online, \ "don't auto-online memory sections"), \ OPT_BOOLEAN('f', "force", ¶m.force, \ - "attempt to offline memory sections before reconfiguration") + "attempt to offline memory sections before reconfiguration"), \ +OPT_BOOLEAN('C', "check-config", ¶m.check_config, \ + "use config files to determine parameters for the operation") #define CREATE_OPTIONS() \ OPT_STRING('s', "size", ¶m.size, "size", "size to switch the device to"), \ @@ -218,6 +226,137 @@ err: return rc; } +static int conf_string_to_bool(const char *str) +{ + if (!str) + return INT_MAX; + if (strncmp(str, "t", 1) == 0 || + strncmp(str, "T", 1) == 0 || + strncmp(str, "y", 1) == 0 || + strncmp(str, "Y", 1) == 0 || + strncmp(str, "1", 1) == 0) + return true; + if (strncmp(str, "f", 1) == 0 || + strncmp(str, "F", 1) == 0 || + strncmp(str, "n", 1) == 0 || + strncmp(str, "N", 1) == 0 || + strncmp(str, "0", 1) == 0) + return false; + return INT_MAX; +} + +#define conf_assign_inverted_bool(p, conf_var) \ +do { \ + if (conf_string_to_bool(conf_var) != INT_MAX) \ + param.p = !conf_string_to_bool(conf_var); \ +} while(0) + +static int parse_config_reconfig_set_params(struct daxctl_ctx *ctx, const char *device, + const char *uuid) +{ + const char *conf_online = NULL, *conf_movable = NULL; + const struct config configs[] = { + CONF_SEARCH(CONF_SECTION, CONF_NVDIMM_UUID_STR, uuid, + "mode", ¶m.mode, NULL), + CONF_SEARCH(CONF_SECTION, CONF_NVDIMM_UUID_STR, uuid, + "online", &conf_online, NULL), + CONF_SEARCH(CONF_SECTION, CONF_NVDIMM_UUID_STR, uuid, + "movable", &conf_movable, NULL), + CONF_END(), + }; + const char *prefix = "./", *daxctl_configs; + int rc; + + daxctl_configs = daxctl_get_configs_dir(ctx); + if (daxctl_configs == NULL) + return 0; + + rc = parse_configs_prefix(daxctl_configs, prefix, configs); + if (rc < 0) + return rc; + + conf_assign_inverted_bool(no_online, conf_online); + conf_assign_inverted_bool(no_movable, conf_movable); + + return 0; +} + +static bool daxctl_ndns_has_device(struct ndctl_namespace *ndns, + const char *device) +{ + struct daxctl_region *dax_region; + struct ndctl_dax *dax; + + dax = ndctl_namespace_get_dax(ndns); + if (!dax) + return false; + + dax_region = ndctl_dax_get_daxctl_region(dax); + if (dax_region) { + struct daxctl_dev *dev; + + dev = daxctl_dev_get_first(dax_region); + if (dev) { + if (strcmp(daxctl_dev_get_devname(dev), device) == 0) + return true; + } + } + return false; +} + +static int parse_config_reconfig(struct daxctl_ctx *ctx, const char *device) +{ + struct ndctl_namespace *ndns; + struct ndctl_ctx *ndctl_ctx; + struct ndctl_region *region; + struct ndctl_bus *bus; + struct ndctl_dax *dax; + int rc, found = 0; + char uuid_buf[40]; + uuid_t uuid; + + if (strcmp(device, "all") == 0) + return 0; + + rc = ndctl_new(&ndctl_ctx); + if (rc < 0) + return rc; + + ndctl_bus_foreach(ndctl_ctx, bus) { + ndctl_region_foreach(bus, region) { + ndctl_namespace_foreach(region, ndns) { + if (daxctl_ndns_has_device(ndns, device)) { + dax = ndctl_namespace_get_dax(ndns); + if (!dax) + continue; + ndctl_dax_get_uuid(dax, uuid); + found = 1; + } + } + } + } + + if (!found) { + fprintf(stderr, "no UUID match for %s found in config files\n", + device); + return 0; + } + + uuid_unparse(uuid, uuid_buf); + return parse_config_reconfig_set_params(ctx, device, uuid_buf); +} + +static int parse_device_config(struct daxctl_ctx *ctx, const char *device, + enum device_action action) +{ + switch (action) { + case ACTION_RECONFIG: + return parse_config_reconfig(ctx, device); + default: + return 0; + } +} + static const char *parse_device_options(int argc, const char **argv, enum device_action action, const struct option *options, const char *usage, struct daxctl_ctx *ctx) @@ -228,8 +367,11 @@ static const char *parse_device_options(int argc, const char **argv, }; unsigned long long units = 1; int i, rc = 0; + char *device; argc = parse_options(argc, argv, options, u, 0); + device = basename(argv[0]); + /* Handle action-agnostic non-option arguments */ if (argc == 0 && @@ -279,6 +421,34 @@ static const char *parse_device_options(int argc, const char **argv, if (param.human) flags |= UTIL_JSON_HUMAN; + /* Scan config file(s) for options. This sets param.foo accordingly */ + if (param.check_config) { + if (param.mode || param.no_online || param.no_movable) { + fprintf(stderr, + "%s: -C cannot be used with --mode, --(no-)movable, or --(no-)online\n", + device); + usage_with_options(u, options); + } + rc = parse_device_config(ctx, device, action); + if (rc) { + fprintf(stderr, "error parsing config file: %s\n", + strerror(-rc)); + return NULL; + } + if (!param.mode && !param.no_online && !param.no_movable) { + fprintf(stderr, "%s: missing or malformed config section\n", + device); + /* + * Exit with success since the most common case is there is + * no config defined for this device, and we don't want to + * treat that as an error. There isn't an easy way currently + * to distinguish between a malformed config entry from a + * completely missing config section. + */ + exit(0); + } + } + /* Handle action-specific options */ switch (action) { case ACTION_RECONFIG: @@ -336,7 +506,7 @@ static const char *parse_device_options(int argc, const char **argv, return NULL; } - return argv[0]; + return device; } static int dev_online_memory(struct daxctl_dev *dev) diff --git a/daxctl/Makefile.am b/daxctl/Makefile.am index a5c6de1..ac97cd1 100644 --- a/daxctl/Makefile.am +++ b/daxctl/Makefile.am @@ -23,6 +23,7 @@ daxctl_SOURCES =\ daxctl_LDADD =\ lib/libdaxctl.la \ + ../ndctl/lib/libndctl.la \ ../libutil.a \ $(UUID_LIBS) \ $(KMOD_LIBS) \ From patchwork Mon Dec 6 22:28:30 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Verma, Vishal L" X-Patchwork-Id: 12659959 Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1380B2CA8 for ; Mon, 6 Dec 2021 22:28:48 +0000 (UTC) X-IronPort-AV: E=McAfee;i="6200,9189,10190"; a="300804844" X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="300804844" Received: from orsmga007.jf.intel.com ([10.7.209.58]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:46 -0800 X-IronPort-AV: E=Sophos;i="5.87,292,1631602800"; d="scan'208";a="502310473" Received: from ponufryk-mobl1.amr.corp.intel.com (HELO vverma7-desk.amr.corp.intel.com) ([10.251.133.29]) by orsmga007-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Dec 2021 14:28:46 -0800 From: Vishal Verma To: Cc: Dan Williams , QI Fuli , fenghua.hu@intel.com, Vishal Verma , QI Fuli Subject: [ndctl PATCH v2 12/12] daxctl: add systemd service and udev rule for automatic reconfiguration Date: Mon, 6 Dec 2021 15:28:30 -0700 Message-Id: <20211206222830.2266018-13-vishal.l.verma@intel.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211206222830.2266018-1-vishal.l.verma@intel.com> References: <20211206222830.2266018-1-vishal.l.verma@intel.com> Precedence: bulk X-Mailing-List: nvdimm@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=4207; h=from:subject; bh=CwjCJ5OMhaZPhOB/59KJO8aVDvUQ4RNpyfO3a3jflO0=; b=owGbwMvMwCXGf25diOft7jLG02pJDInr+vry8/hqF9yfFPhx8l4WaflE+3lsFXcvtbZ81r1yMd77 8IusjlIWBjEuBlkxRZa/ez4yHpPbns8TmOAIM4eVCWQIAxenAExE6w/Df9/ZDt+qvr5glk96bTJ96V HOt5oihdOyV/M+m7zYV9+9PZ7hf/i8W+23r8RFP5yUMknsZN0SJ0tP83tx2b+2ackkiCp3sAMA X-Developer-Key: i=vishal.l.verma@intel.com; a=openpgp; fpr=F8682BE134C67A12332A2ED07AFA61BEA3B84DFF Install a systemd service that calls "daxctl-reconfigure-device --check-config" with a daxctl device passed in to it via the environment. Install a udev rule that is triggered for every daxctl device, and triggers the above oneshot systemd service. On boot, whenever a daxctl device is found, udev triggers a device-specific systemd service called, for example: daxdev-reconfigure@-dev-dax0.0.service This initiates a daxctl-reconfigure-device with a config lookup for the 'dax0.0' device. If the config has a '[reconfigure-device ]' section, it uses the information in that to set the operating mode of the device. If any device is in an unexpected status, 'journalctl' can be used to view the reconfiguration log for that device, for example: journalctl --unit daxdev-reconfigure@-dev-dax0.0.service Update the RPM spec file to include the newly added files to the RPM build. Cc: QI Fuli Cc: Dan Williams Signed-off-by: Vishal Verma --- configure.ac | 9 ++++++++- daxctl/90-daxctl-device.rules | 1 + daxctl/Makefile.am | 7 +++++++ daxctl/daxdev-reconfigure@.service | 8 ++++++++ ndctl.spec.in | 3 +++ 5 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 daxctl/90-daxctl-device.rules create mode 100644 daxctl/daxdev-reconfigure@.service diff --git a/configure.ac b/configure.ac index e779b51..dcdc15f 100644 --- a/configure.ac +++ b/configure.ac @@ -160,7 +160,7 @@ AC_CHECK_FUNCS([ \ AC_ARG_WITH([systemd], AS_HELP_STRING([--with-systemd], - [Enable systemd functionality (monitor). @<:@default=yes@:>@]), + [Enable systemd functionality. @<:@default=yes@:>@]), [], [with_systemd=yes]) if test "x$with_systemd" = "xyes"; then @@ -186,6 +186,13 @@ daxctl_modprobe_data=daxctl.conf AC_SUBST([daxctl_modprobe_datadir]) AC_SUBST([daxctl_modprobe_data]) +AC_ARG_WITH(udevrulesdir, + [AS_HELP_STRING([--with-udevrulesdir=DIR], [udev rules.d directory])], + [UDEVRULESDIR="$withval"], + [UDEVRULESDIR='${prefix}/lib/udev/rules.d'] +) +AC_SUBST(UDEVRULESDIR) + AC_ARG_WITH([keyutils], AS_HELP_STRING([--with-keyutils], [Enable keyutils functionality (security). @<:@default=yes@:>@]), [], [with_keyutils=yes]) diff --git a/daxctl/90-daxctl-device.rules b/daxctl/90-daxctl-device.rules new file mode 100644 index 0000000..ee0670f --- /dev/null +++ b/daxctl/90-daxctl-device.rules @@ -0,0 +1 @@ +ACTION=="add", SUBSYSTEM=="dax", TAG+="systemd", ENV{SYSTEMD_WANTS}="daxdev-reconfigure@$env{DEVNAME}.service" diff --git a/daxctl/Makefile.am b/daxctl/Makefile.am index ac97cd1..6d41016 100644 --- a/daxctl/Makefile.am +++ b/daxctl/Makefile.am @@ -28,3 +28,10 @@ daxctl_LDADD =\ $(UUID_LIBS) \ $(KMOD_LIBS) \ $(JSON_LIBS) + +udevrulesdir = $(UDEVRULESDIR) +udevrules_DATA = 90-daxctl-device.rules + +if ENABLE_SYSTEMD_UNITS +systemd_unit_DATA = daxdev-reconfigure@.service +endif diff --git a/daxctl/daxdev-reconfigure@.service b/daxctl/daxdev-reconfigure@.service new file mode 100644 index 0000000..13d570c --- /dev/null +++ b/daxctl/daxdev-reconfigure@.service @@ -0,0 +1,8 @@ +[Unit] +Description=Automatic daxctl device reconfiguration +Documentation=man:daxctl-reconfigure-device(1) + +[Service] +Type=forking +GuessMainPID=false +ExecStart=/bin/sh -c "exec daxctl reconfigure-device --check-config %I" diff --git a/ndctl.spec.in b/ndctl.spec.in index 07c36ec..2c33664 100644 --- a/ndctl.spec.in +++ b/ndctl.spec.in @@ -26,6 +26,7 @@ BuildRequires: pkgconfig(json-c) BuildRequires: pkgconfig(bash-completion) BuildRequires: pkgconfig(systemd) BuildRequires: keyutils-libs-devel +BuildRequires: systemd-rpm-macros %description Utility library for managing the "libnvdimm" subsystem. The "libnvdimm" @@ -126,6 +127,8 @@ make check %{_bindir}/daxctl %{_mandir}/man1/daxctl* %{_datadir}/daxctl/daxctl.conf +%{_unitdir}/daxdev-reconfigure@.service +%config %{_udevrulesdir}/90-daxctl-device.rules %files -n LNAME %defattr(-,root,root)