diff --git a/libnvme/src/nvme/private.h b/libnvme/src/nvme/private.h index 403e998ec0..749c6c616b 100644 --- a/libnvme/src/nvme/private.h +++ b/libnvme/src/nvme/private.h @@ -128,7 +128,7 @@ struct nvme_transport_handle { struct nvme_log *log; }; -struct nvme_path { +struct nvme_path { /*!generate-accessors*/ struct list_node entry; struct list_node nentry; @@ -140,7 +140,7 @@ struct nvme_path { char *ana_state; char *numa_nodes; int grpid; - int queue_depth; + int queue_depth; //!accessors:none }; struct nvme_ns_head { @@ -150,7 +150,7 @@ struct nvme_ns_head { char *sysfs_dir; }; -struct nvme_ns { +struct nvme_ns { /*!generate-accessors*/ struct list_node entry; struct nvme_subsystem *s; @@ -161,7 +161,7 @@ struct nvme_ns { struct nvme_transport_handle *hdl; __u32 nsid; char *name; - char *generic_name; + char *generic_name; //!accessors:none char *sysfs_dir; int lba_shift; @@ -176,7 +176,7 @@ struct nvme_ns { enum nvme_csi csi; }; -struct nvme_ctrl { +struct nvme_ctrl { /*!generate-accessors*/ struct list_node entry; struct list_head paths; struct list_head namespaces; @@ -186,16 +186,16 @@ struct nvme_ctrl { struct nvme_transport_handle *hdl; char *name; char *sysfs_dir; - char *address; + char *address; //!accessors:none char *firmware; char *model; - char *state; + char *state; //!accessors:none char *numa_node; char *queue_count; char *serial; char *sqsize; char *transport; - char *subsysnqn; + char *subsysnqn; //!accessors:none char *traddr; char *trsvcid; char *dhchap_host_key; @@ -206,7 +206,7 @@ struct nvme_ctrl { char *cntrltype; char *cntlid; char *dctype; - char *phy_slot; + char *phy_slot; //!accessors:none char *host_traddr; char *host_iface; bool discovery_ctrl; @@ -216,7 +216,7 @@ struct nvme_ctrl { struct nvme_fabrics_config cfg; }; -struct nvme_subsystem { +struct nvme_subsystem { /*!generate-accessors*/ struct list_node entry; struct list_head ctrls; struct list_head namespaces; @@ -233,7 +233,7 @@ struct nvme_subsystem { char *iopolicy; }; -struct nvme_host { +struct nvme_host { /*!generate-accessors*/ struct list_node entry; struct list_head subsystems; struct nvme_global_ctx *ctx; @@ -242,12 +242,12 @@ struct nvme_host { char *hostid; char *dhchap_host_key; char *hostsymname; - bool pdc_enabled; + bool pdc_enabled; //!accessors:none bool pdc_enabled_valid; /* set if pdc_enabled doesn't have an undefined * value */ }; -struct nvme_fabric_options { +struct nvme_fabric_options { /*!generate-accessors*/ bool cntlid; bool concat; bool ctrl_loss_tmo; diff --git a/libnvme/tools/generator/generate-accessors-exclude.list b/libnvme/tools/generator/generate-accessors-exclude.list deleted file mode 100644 index 6fbbf1228c..0000000000 --- a/libnvme/tools/generator/generate-accessors-exclude.list +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -nvme_ns::generic_name -nvme_ctrl::state -nvme_ctrl::address -nvme_ctrl::phy_slot -nvme_ctrl::subsysnqn -nvme_path::queue_depth -nvme_host::pdc_enabled - diff --git a/libnvme/tools/generator/generate-accessors-include.list b/libnvme/tools/generator/generate-accessors-include.list deleted file mode 100644 index 5167503b8c..0000000000 --- a/libnvme/tools/generator/generate-accessors-include.list +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -nvme_path -nvme_ns -nvme_ctrl -nvme_subsystem -nvme_host -nvme_fabric_options diff --git a/libnvme/tools/generator/generate-accessors.c b/libnvme/tools/generator/generate-accessors.c deleted file mode 100644 index 149e151242..0000000000 --- a/libnvme/tools/generator/generate-accessors.c +++ /dev/null @@ -1,2124 +0,0 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later -/** - * This file is part of libnvme. - * - * Copyright (c) 2025, Dell Technologies Inc. or its subsidiaries. - * - * Authors: Martin Belanger - * - * This program parses C header files and generates accessor - * functions (setter/getter) for each member found within. - * - * Limitations: - * - Does not support typedef struct. For example, - * typedef struct { - * ... - * } my_struct_t; - * - * - Does not support struct within struct. For example, - * struct my_struct { - * struct another_struct { - * ... - * } my_var; - * ... - * }; - * - * Example usage: - * ./generate-accessors private.h - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef NVME_HAVE_SENDFILE -#include -#endif - -#define OUTPUT_FNAME_DEFAULT_C "accessors.c" -#define OUTPUT_FNAME_DEFAULT_H "accessors.h" -#define OUTPUT_FNAME_DEFAULT_LD "accessors.ld" - -#define STRUCT_RE "struct[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\\{([^}]*)\\}[[:space:]]*;" -#define CHAR_ARRAY_RE "^(const[[:space:]]+)?char[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\\[[[:space:]]*([A-Za-z0-9_]+)[[:space:]]*\\][[:space:]]*;" -#define MEMBER_RE "^(const[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*)([*[:space:]]+)([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*;" - -#define SPACES " \t\n\r" -#define streq(a, b) (strcmp((a), (b)) == 0) - -/** - * Function naming convention: - * This controls whether to generate the functions as: - * nvme_foo_get() / nvme_foo_set() - * Or: - * nvme_get_foo() / nvme_set_foo() - */ -#define SET_FMT "%s_set_%s" /* alternate name: "%s_%s_set" */ -#define GET_FMT "%s_get_%s" /* alternate name: "%s_%s_get" */ - -/* checkpatch requires C99 // SPDX in *.c and block-style SPDX in *.h */ -static const char *spdx_c = "// SPDX-License-Identifier: LGPL-2.1-or-later"; -static const char *spdx_h = "/* SPDX-License-Identifier: LGPL-2.1-or-later */"; -static const char *spdx_ld = "# SPDX-License-Identifier: LGPL-2.1-or-later"; - -static const char *banner = - "/**\n" - " * This file is part of libnvme.\n" - " *\n" - " * Copyright (c) 2025, Dell Technologies Inc. or its subsidiaries.\n" - " * Authors: Martin Belanger \n" - " *\n" - " * ____ _ _ ____ _\n" - " * / ___| ___ _ __ ___ _ __ __ _| |_ ___ __| | / ___|___ __| | ___\n" - " * | | _ / _ \\ '_ \\ / _ \\ '__/ _` | __/ _ \\/ _` | | | / _ \\ / _` |/ _ \\\n" - " * | |_| | __/ | | | __/ | | (_| | || __/ (_| | | |__| (_) | (_| | __/\n" - " * \\____|\\___|_| |_|\\___|_| \\__,_|\\__\\___|\\__,_| \\____\\___/ \\__,_|\\___|\n" - " *\n" - " * Auto-generated struct member accessors (setter/getter)\n" - " *\n" - " * To update run: meson compile -C [BUILD-DIR] update-accessors\n" - " * Or: make update-accessors\n" - " */"; - -/** - * @brief Remove leading whitespace characters from string - * in-place. - * - * Removes leading whitespace defined by SPACES by returning - * a pointer within @s to the first character that is not a - * whitespace. - * - * @param s: The writable string to trim. If NULL, return NULL. - * - * @return Pointer to the first character of s that is not a - * white space, or NULL if @s is NULL. - */ -static inline char *ltrim(const char *s) -{ - return s ? (char *)s + strspn(s, SPACES) : NULL; -} - -/** - * @brief Remove trailing whitespace characters from a string in-place. - * - * Removes trailing whitespace defined by SPACES by writing a - * terminating NUL byte at the first trailing whitespace position. - * - * @param s: The writable string to trim. If NULL, return NULL. - * - * @return The same pointer @s, or NULL if @s is NULL. - */ -static char *rtrim(char *s) -{ - if (!s) - return NULL; - - char *p0 = s; - - for (char *p = s; *p; p++) { - if (!strchr(SPACES, *p)) - p0 = p + 1; - } - *p0 = '\0'; - - return s; -} - -/** - * @brief Trim both leading and trailing whitespace from a string. - * - * Uses ltrim to skip leading whitespace (returns pointer into original - * string) and rtrim to remove trailing whitespace in-place. - * - * @param s: The string to trim. If NULL, returns NULL. - * - * @return Pointer to the trimmed string (may be interior of original), - * or NULL if @s is NULL. - */ -static char *trim(char *s) -{ - return s ? rtrim(ltrim(s)) : NULL; -} - -/** - * @brief Strip inline C++-style comments ("// ...") from a line. - * - * If the substring "//" is found in @line, it is replaced by a NUL - * terminator so the remainder is ignored. - * - * @param line: Line buffer to modify (in-place). - * - * @return Pointer to the (possibly truncated) line. - */ -static char *trim_inline_comments(char *line) -{ - char *p = strstr(line, "//"); - - if (p) - *p = '\0'; - return line; -} - -/** - * @brief Remove C-style block comments (like this comment) from - * a text buffer. - * - * Replaces all comment characters with space characters to - * preserve offsets while removing comment content. - * - * @param text: The buffer to clean (modified in-place). If no - * match is found the text pointed to by @text is - * left alone. - */ -static void mask_c_comments(char *text) -{ - char *p = text; - - while ((p = strstr(p, "/*")) != NULL) { - char *end = strstr(p + 2, "*/"); - - if (!end) - break; - memset(p, ' ', end - p + 2); - p = end + 2; - } -} - -/** - * @brief Convert a string to uppercase in-place. - * - * Iterates each character of @s and transforms it to uppercase - * using toupper(). - * - * @param s: The string to convert (modified in-place). Must be - * writable. - * - * @return Pointer to the modified @s. - */ -static char *to_uppercase(char *s) -{ - if (!s) - return s; - for (int i = 0; s[i] != '\0'; i++) - s[i] = toupper((unsigned char)s[i]); - return s; -} - -/** - * @brief Sanitize a string to form a valid C identifier. - * - * This function modifies the given string in place so that all characters - * conform to the rules of a valid C variable name (identifier): - * - The first character must be a letter (A–Z, a–z) or underscore ('_'). - * - Subsequent characters may be letters, digits, or underscores. - * - * Any character that violates these rules is replaced with an underscore ('_'). - * The string is always modified in place; no new memory is allocated. - * - * @param s Pointer to the NUL-terminated string to sanitize. If @s is NULL or - * points to an empty string, the function does nothing. - * - * @note This function does not check for C keywords or identifier length limits. - * - * @code - * char name[] = "123bad-name!"; - * sanitize_identifier(name); - * // Result: "_23bad_name_" - * @endcode - */ -static const char *sanitize_identifier(char *s) -{ - if (s == NULL || *s == '\0') - return s; - - // The first character must be a letter or underscore - if (!isalpha((unsigned char)s[0]) && s[0] != '_') - s[0] = '_'; - - // Remaining characters: letters, digits, underscores allowed - for (char *p = s + 1; *p; ++p) { - if (!isalnum((unsigned char)*p) && *p != '_') - *p = '_'; - } - - return s; -} - -/** - * @brief Duplicate a C string safely. - * - * Allocates memory for and returns a copy of @s including the - * terminating NUL. Uses malloc and memcpy to copy the entire buffer. - * - * The POSIX strdup() has unpredictable behavior when provided - * with a NULL pointer. This function adds a NULL check for - * safety. Also, strdup() is a POSIX extension that may not be - * available on all platforms. Therefore, this function uses - * malloc() and memcpy() instead of invoking strdup(). - * - * @param s: Source string to duplicate. If NULL, returns NULL. - * - * @return Newly allocated copy of @s, or NULL on allocation failure or - * if @s is NULL. Caller must free(). - */ -static char *safe_strdup(const char *s) -{ - if (!s) - return NULL; - - size_t len = strlen(s) + 1; /* length including NUL-terminator */ - char *new_string = (char *)malloc(len); - - if (!new_string) - return NULL; /* Return NULL on allocation failure */ - - memcpy(new_string, s, len); /* Copy the string incl. NUL-terminator */ - - return new_string; -} - -/** - * @brief Duplicate up to @size characters of a C string safely. - * - * Copies at most @size characters from @s into a newly allocated, - * NUL-terminated buffer. If @s is shorter than @size, copies only - * up to the terminating NUL. - * - * The POSIX strndup() has unpredictable behavior when provided - * with a NULL pointer. This function adds a NULL check for - * safety. Also, strndup() is a POSIX extension that may not be - * available on all platforms. Therefore, this function uses - * malloc() and memcpy() instead of invoking strndup(). - * - * @param s: Source string to duplicate. If NULL, returns NULL. - * @param size: Maximum number of characters to consider from @s. - * - * @return Newly allocated NUL-terminated copy, or NULL on - * allocation failure or if @s is NULL. Caller must - * free(). - */ -static char *safe_strndup(const char *s, size_t size) -{ - if (!s) - return NULL; - - size_t len = strnlen(s, size); - char *new_string = malloc(len + 1); - - if (!new_string) - return NULL; - - memcpy(new_string, s, len); - new_string[len] = '\0'; - - return new_string; -} - -/** - * @brief Test whether a string contains only decimal digits. - * - * Returns false for NULL or empty string. - * - * @param s: Null-terminated string to test. - * - * @return true if every character in @s is a decimal digit (0-9), - * false otherwise. - */ -static bool str_is_all_numbers(const char *s) -{ - if (!s || *s == '\0') - return false; - - for (; *s != '\0'; s++) { - if (!isdigit((unsigned char)*s)) - return false; - } - - return true; -} - -/** - * @brief Return pointer to filename component within a path. - * - * Finds the last '/' in @path and returns pointer to the next - * character; if no '/' is found returns the original @path. - * - * @param path: Input path string (must be NUL-terminated). - * - * @return Pointer to filename portion (not newly allocated). - */ -static const char *get_filename(const char *path) -{ - const char *slash = strrchr(path, '/'); - - return slash ? slash + 1 : path; -} - -/** - * @brief Create directories recursively (mkdir -p behavior). - * - * Walks the path components and creates each intermediate directory - * with the specified @mode. If @path is ".", returns success. - * - * @param path: Path to create (e.g., "/tmp/a/b"). - * @param mode: Permissions bits for created directories (as for mkdir). - * - * @return true on success (directories created or already exist), - * false on error (errno is set). - */ -static bool mkdir_p(const char *path, mode_t mode) -{ - bool ok = false; - char *tmp; - char *p = NULL; - size_t len; - - if (streq(path, ".")) - return true; - - if (!path || !*path) { - errno = EINVAL; - return false; - } - - /* Copy path to temporary buffer */ - tmp = safe_strdup(path); - len = strlen(tmp); - if (tmp[len - 1] == '/') - tmp[len - 1] = '\0'; /* remove trailing slash */ - - for (p = tmp + 1; *p; p++) { - if (*p == '/') { - *p = '\0'; - /* Attempt to create directory */ - if (mkdir(tmp, mode) != 0) { - if (errno != EEXIST) - goto mkdir_out; - } - *p = '/'; - } - } - - /* Create final directory */ - if (mkdir(tmp, mode) != 0) { - if (errno != EEXIST) - goto mkdir_out; - } - - ok = true; - -mkdir_out: - free(tmp); - return ok; -} - -/** - * @brief Create directories to hold file specified by @fullpath - * - * Given a path and file name (@fullpath), create the - * directories to hold the file. This is done by splitting the - * file portion from @fullpath and creating the directory tree. - * - * @param fullpath: Directories + file name. - * @param mode: Permissions bits for created directories (as for mkdir). - * - * @return true on success (directories created or already exist), - * false on error (errno is set). - */ -static bool mkdir_fullpath(const char *fullpath, mode_t mode) -{ - char saved; - bool ok; - char *fname = (char *)get_filename(fullpath); - - /* Check whether it's just a file name w/o a path. */ - if (fname == fullpath) - return true; - - saved = fname[0]; - fname[0] = '\0'; /* split file name from path */ - ok = mkdir_p(fullpath, mode); - fname[0] = saved; /* restore full path */ - - return ok; -} - -/** - * @brief Read entire file into a newly-allocated buffer. - * - * Opens the file at @path and reads all bytes into a buffer that is - * NUL-terminated. Exits the program on failure to open the file. - * - * @param path: Path to the file to read. - * - * @return Pointer to a malloc()-allocated buffer containing the file - * contents (NUL-terminated). Caller must free(). On error the - * program exits with EXIT_FAILURE. - */ -static char *read_file(const char *path) -{ - long len; - char *buf; - FILE *f = fopen(path, "rb"); - - if (!f) { - perror(path); - exit(EXIT_FAILURE); - } - fseek(f, 0, SEEK_END); - len = ftell(f); - rewind(f); - buf = malloc(len + 1); - len = fread(buf, 1, len, f); - buf[len] = '\0'; - fclose(f); - return buf; -} - - -/******************************************************************************/ - - -/** - * @brief Compute the length of a regex match represented by regmatch_t. - * - * Returns zero if the match is invalid (rm_so or rm_eo negative or - * rm_eo < rm_so). - * - * @param m: Pointer to regmatch_t describing the match. - * - * @return Size in bytes of the match, or 0 if invalid. - */ -static size_t regmatch_size(const regmatch_t *m) -{ - bool invalid = m->rm_so < 0 || m->rm_eo < 0 || m->rm_eo < m->rm_so; - - return invalid ? 0 : m->rm_eo - m->rm_so; -} - -/** - * @brief Duplicate the substring matched by a regmatch_t. - * - * Allocates a new NUL-terminated buffer and copies the matched span - * from @src. - * - * @param src: Source string used in the regexec(). - * @param m: Pointer to regmatch_t describing the match. - * - * @return Newly allocated string containing the match, or NULL if the - * match has zero length. Caller must free(). - */ -static char *regmatch_strdup(const char *src, const regmatch_t *m) -{ - size_t len = regmatch_size(m); - - return len ? safe_strndup(src + m->rm_so, len) : NULL; -} - -/** - * @brief Check whether a regex match contains a specific character. - * - * Iterates characters inside the matched span and returns true if - * character @c appears within. - * - * @param src: Source string used for the match. - * @param m: Match information. - * @param c: Character to search for. - * - * @return true if @c present in match span, false otherwise. - */ -static bool regmatch_contains_char(const char *src, const regmatch_t *m, char c) -{ - size_t len = regmatch_size(m); - - if (!len) - return false; - - const char *arr = src + m->rm_so; - - for (size_t i = 0; (arr[i] != '\0') && (i < len); i++) { - if (arr[i] == c) - return true; - } - return false; -} - -/** - * @brief Test whether the matched substring begins with a given prefix. - * - * Compares the first bytes of the match against @s. - * - * @param src: Source string used for matching. - * @param m: Match information. - * @param s: Prefix to compare. - * - * @return true if the matched substring starts with @s, false - * otherwise. - */ -static bool regmatch_startswith(const char *src, - const regmatch_t *m, - const char *s) -{ - size_t size; - size_t len = regmatch_size(m); - - if (len == 0) - return false; - - size = strlen(s); - if (len < size) - return false; - - const char *arr = src + m->rm_so; - - return !strncmp(arr, s, size); -} - - -/******************************************************************************/ - - -typedef struct StringList { - const char **strings; /* Pointer to an array of char pointers */ - size_t count; /* Current number of strings */ - size_t capacity; /* Allocated capacity for strings */ -} StringList_t; - - -/** - * @brief Initialize a StringList_t object. - * - * Allocates the internal array with at least @initial_capacity - * entries (minimum STRINGLIST_INITIAL_CAPACITY). The list is - * empty after initialization. - * - * @param list: Pointer to the list to initialize. - * @param initial_capacity: Desired initial capacity (will be rounded - * up to at least STRINGLIST_INITIAL_CAPACITY). - */ -#define STRINGLIST_INITIAL_CAPACITY 8 -static void strlst_init(StringList_t *list, size_t initial_capacity) -{ - if (initial_capacity < STRINGLIST_INITIAL_CAPACITY) - initial_capacity = STRINGLIST_INITIAL_CAPACITY; - list->strings = (const char **)calloc(initial_capacity, sizeof(char *)); - list->count = 0; - list->capacity = initial_capacity; -} - -/** - * @brief Append a string to a StringList_t. - * - * If list capacity is exhausted, it doubles the capacity and - * reallocates. The input string is set either by stealing the - * passed pointer or by duplicating it depending on @steal. - * - * @param list: Pointer to the string list. - * @param string: Null-terminated string to add. - * @param steal: If true, take ownership of @string pointer (no copy). - */ -static void strlst_add(StringList_t *list, const char *string, bool steal) -{ - if (!string) - return; /* Do nothing is string is NULL */ - - if (list->count == list->capacity) { - /* Reallocate to double capacity */ - list->capacity *= 2; - list->strings = - (const char **)realloc(list->strings, - list->capacity * sizeof(char *)); - for (size_t i = list->count; i < list->capacity; i++) - list->strings[i] = NULL; - } - - /* Allocate memory for the new string and copy its content */ - list->strings[list->count++] = steal ? string : safe_strdup(string); -} - -/** - * @brief Remove all strings from the list and free them. - * - * Frees each stored string and resets count to zero but leaves the - * allocated array in place so the list can be reused. - * - * @param list: Pointer to the string list to clear. - */ -static void strlst_clear(StringList_t *list) -{ - for (size_t i = 0; i < list->count; i++) { - free((void *)list->strings[i]); - list->strings[i] = NULL; - } - - list->count = 0; -} - -/** - * @brief Free a StringList_t and its contents. - * - * Frees all stored strings and the internal array and resets the list - * fields to indicate an empty/unallocated list. - * - * @param list: Pointer to the list to free. - */ -static void strlst_free(StringList_t *list) -{ - strlst_clear(list); - free(list->strings); - list->strings = NULL; - list->capacity = 0; -} - -/** - * @brief Test if the string list is empty. - * - * @param list: Pointer to the string list. - * - * @return true if list contains no elements, false otherwise. - */ -#define STRLST_EMPTY(list) ((list)->count == 0) - -/** - * @brief Iterate over a StringList_t returning next element. - * - * @param sl: Pointer to the string list. - * @param s: Pointer that gets updated with char* at current - * index or NULL if end reached. - */ -#define STRLST_FOREACH(sl, s) \ - for (size_t __str_next = 0; \ - ({ \ - (s) = (__str_next >= (sl)->count) \ - ? NULL : (sl)->strings[__str_next++]; \ - (s) != NULL; \ - });) - -/** - * @brief Check whether a list contains a given string. - * - * Compares strings using strcmp (streq macro). - * - * @param list: Pointer to the string list. - * @param s: String to search for. - * - * @return true if @s exists in the list, false otherwise. - */ -static bool strlst_contains(const StringList_t *list, const char *s) -{ - const char *str; - - STRLST_FOREACH(list, str) { - if (streq(s, str)) - return true; - } - return false; -} - -/** - * @brief Load strings from a text file into a StringList_t. - * - * Reads the file line-by-line. For each line, trims whitespace and - * skips empty lines or lines starting with '#'. Remaining lines are - * added to @list. - * - * @param list: Pointer to the initialized StringList_t to append to. - * @param filename: Path to the file to read. If NULL, the - * function returns immediately. - */ -static void strlst_load(StringList_t *list, const char *filename) -{ - char line[LINE_MAX]; - FILE *f; - - if (!filename) - return; - - f = fopen(filename, "r"); - if (!f) { - fprintf(stderr, - "Warning: could not open file '%s'\n", filename); - return; - } - - while (fgets(line, sizeof(line), f)) { - /* Strip whitespace and comments */ - char *p = trim(line); - - if (*p == '\0' || *p == '#') - continue; - strlst_add(list, p, false); - } - - fclose(f); -} - -/** - * @brief Check whether a struct or struct member is excluded. - * - * The exclusion list may contain either struct names (to exclude the - * whole struct) or entries of the form "StructName::member" to exclude - * individual members. - * - * @param excl_list: Pointer to the exclusion StringList_t. - * @param struct_name: Name of the struct being considered. - * @param member_name: Name of the member (or NULL to check whole struct). - * - * @return true if the struct or member is present in the exclusion list, - * false otherwise. - */ -static bool is_excluded(const StringList_t *excl_list, - const char *struct_name, - const char *member_name) -{ - char key[LINE_MAX]; - - /* First, check if the whole struct is excluded */ - for (size_t i = 0; i < excl_list->count; i++) { - if (streq(excl_list->strings[i], struct_name)) - return true; /* exclude entire struct */ - } - - if (!member_name) - return false; - - /* Second, check if StructName::member is excluded */ - snprintf(key, sizeof(key), "%s::%s", struct_name, member_name); - for (size_t i = 0; i < excl_list->count; i++) { - if (streq(excl_list->strings[i], key)) - return true; /* exclude member of struct */ - } - - return false; -} - -/** - * @brief Decide whether a struct name is included by the include list. - * - * If the include list is empty, everything is considered included. - * - * @param incl_list: Pointer to inclusion StringList_t. - * @param struct_name: Name of the struct to test. - * - * @return true if struct is included (or include list empty), false otherwise. - */ -static bool is_included(const StringList_t *incl_list, const char *struct_name) -{ - /* Note: If include list is empty, then everything is included */ - if (STRLST_EMPTY(incl_list)) - return true; - - return strlst_contains(incl_list, struct_name); -} - - -/******************************************************************************/ - - -typedef struct Conf { - bool verbose; - const char *c_fname; /* Generated output *.c file name */ - const char *h_fname; /* Generated output *.h file name */ - const char *l_fname; /* Generated output *.ld file name */ - const char *prefix; /* Prefix added to each functions */ - StringList_t hdr_files; /* Input header file list */ - StringList_t incl_list; /* Inclusion list (read from --incl) */ - StringList_t excl_list; /* Enclusion list (read from --excl) */ - struct { - regex_t re_struct; /* regex to match struct definitions */ - regex_t re_char_array; /* regex to match char array members */ - regex_t re_member; /* regex to match all other members */ - } re; /* Precompiled regular expressions */ -} Conf_t; - - -/******************************************************************************/ -/** - * The following structures are used to save the structs and members found - * while parsing the header files (*.h). Here's the relationship between - * the different objects. - * - * +--------------------------+ - * | StructList_t | - * |--------------------------| - * | StructInfo_t *items ---> [ array of StructInfo_t ] - * | size_t count | - * | size_t capacity | - * +--------------------------+ - * | - * v - * +--------------------------+ - * | StructInfo_t | - * |--------------------------| - * | char *name | - * | Member_t *members ---> [ array of Member_t ] - * | size_t count | - * | size_t capacity | - * +--------------------------+ - * | - * v - * +--------------------------+ - * | Member_t | - * |--------------------------| - * | char *type | - * | char *name | - * | char *array_size | - * | bool is_char_array | - * | bool is_const | - * +--------------------------+ - */ - -typedef struct Member { - char *type; /* Type of the struct member */ - char *name; /* Name of the struct member */ - char *array_size; /* If member is an array, what is its [size] */ - bool is_char_array; /* Whether the member is an array */ - bool is_const; /* Whether the member is defined as const */ -} Member_t; - -typedef struct StructInfo { - char *name; /* Name of the struct */ - Member_t *members; /* Array of members (each entry is 1 member) */ - size_t count; /* Number of entries in members */ - size_t capacity; /* Allocated capacity for members */ -} StructInfo_t; - -typedef struct StructList { - StructInfo_t *items; /* Array of structs (each entry is 1 struct) */ - size_t count; /* Number of entries in items */ - size_t capacity; /* Allocated capacity for items */ -} StructList_t; - -/** - * @brief Initialize Member_t to default empty values. - * - * Sets pointer fields to NULL and booleans to false. - * - * @param m: Pointer to Member_t to initialize. - */ -static void member_init(Member_t *m) -{ - m->type = NULL; - m->name = NULL; - m->array_size = NULL; - m->is_char_array = false; - m->is_const = false; -} - -/** - * @brief Free and reset Member_t fields. - * - * Frees any allocated strings in @m and reinitializes it. - * - * @param m: Pointer to Member_t to clear. - */ -static void member_clear(Member_t *m) -{ - free(m->type); - free(m->name); - free(m->array_size); - member_init(m); -} - - -/** - * @brief Initialize a StructInfo_t object with default capacity. - * - * Allocates the members array with default capacity - * (MEMBERS_INITIAL_CAPACITY) and sets initial field values. - * - * @param si: Pointer to StructInfo_t to initialize. - */ -#define MEMBERS_INITIAL_CAPACITY 8 -static void struct_info_init(StructInfo_t *si) -{ - si->name = NULL; - si->count = 0; - si->capacity = MEMBERS_INITIAL_CAPACITY; - si->members = malloc(si->capacity * sizeof(Member_t)); - for (size_t m = 0; m < si->capacity; m++) - member_init(&si->members[m]); -} - -/** - * @brief Clear the contents (members and name) of a StructInfo_t. - * - * Frees per-member allocations and the name string, but leaves the - * struct ready for reuse (members array kept). - * - * @param si: Pointer to StructInfo_t to clear. - */ -static void struct_info_clear(StructInfo_t *si) -{ - for (size_t i = 0; i < si->count; ++i) - member_clear(&si->members[i]); - si->count = 0; - free(si->name); - si->name = NULL; -} - -/** - * @brief Free all resources used by a StructInfo_t. - * - * Frees members and the members array, resets pointers and capacity. - * - * @param si: Pointer to StructInfo_t to free. - */ -static void struct_info_free(StructInfo_t *si) -{ - for (size_t i = 0; i < si->capacity; ++i) - member_clear(&si->members[i]); - si->count = 0; - free(si->members); - si->members = NULL; - - free(si->name); - si->name = NULL; - - si->capacity = 0; -} - -/** - * @brief Add a member to a StructInfo_t, growing the array if needed. - * - * Adds a new Member_t to @si. If capacity is insufficient the members - * array is reallocated (doubled). The new Member_t's type and name are - * set either by stealing the passed pointers or by duplicating them - * depending on @steal_type/@steal_name. - * - * @param si: Pointer to StructInfo_t to append to. - * @param type: String describing the member type. - * @param name: Member name string. - * @param steal_type: If true, take ownership of @type pointer (no copy). - * @param steal_name: If true, take ownership of @name pointer (no copy). - * - * @return Pointer to the newly added Member_t. - */ -static Member_t *struct_info_member_add(StructInfo_t *si, - char *type, - char *name, - bool steal_type, - bool steal_name) -{ - Member_t *m; - - if (si->count == si->capacity) { - /* Reallocate to double capacity */ - si->capacity *= 2; - si->members = - (Member_t *)realloc(si->members, - si->capacity * sizeof(Member_t)); - for (size_t i = si->count; i < si->capacity; i++) - member_init(&si->members[i]); - } - m = &si->members[si->count++]; - m->type = steal_type ? type : safe_strdup(type); - m->name = steal_name ? name : safe_strdup(name); - return m; -} - -/** - * @brief Test whether a StructInfo_t contains no members. - * - * @param si: Pointer to StructInfo_t. - * - * @return true if si has zero members, false otherwise. - */ -#define STRUCT_INFO_EMPTY(si) ((si)->count == 0) - - -/** - * @brief Initialize a StructList_t with default capacity. - * - * Allocates the items array and initializes contained StructInfo_t - * entries. - * - * @param sl: Pointer to StructList_t to initialize. - */ -static void struct_list_init(StructList_t *sl) -{ - sl->count = 0; - sl->capacity = 16; - sl->items = malloc(sl->capacity * sizeof(StructInfo_t)); - for (size_t i = 0; i < sl->capacity; i++) - struct_info_init(&sl->items[i]); -} - - -/** - * @brief Clear all StructInfo_t elements in a StructList_t. - * - * Calls struct_info_clear on each existing item and resets count to 0. - * - * @param sl: Pointer to StructList_t to clear. - */ -static void struct_list_clear(StructList_t *sl) -{ - for (size_t i = 0; i < sl->count; ++i) - struct_info_clear(&sl->items[i]); - sl->count = 0; -} - -/** - * @brief Free all resources used by a StructList_t. - * - * Frees each StructInfo_t and the items array and resets list fields. - * - * @param sl: Pointer to StructList_t to free. - */ -static void struct_list_free(StructList_t *sl) -{ - for (size_t i = 0; i < sl->capacity; ++i) - struct_info_free(&sl->items[i]); - free(sl->items); - sl->items = NULL; - sl->count = 0; - sl->capacity = 0; -} - -/** - * @brief Add a new StructInfo_t entry to a StructList_t. - * - * If the list capacity is exhausted it doubles capacity and reallocates - * the items array. The struct name is either stolen or duplicated - * depending on @steal. - * - * @param sl: Pointer to StructList_t. - * @param struct_name: Name of the struct to add. - * @param steal: If true, take ownership of @struct_name pointer. - * - * @return Pointer to the newly added StructInfo_t. - */ -static StructInfo_t *struct_list_struct_add(StructList_t *sl, - char *struct_name, - bool steal) -{ - StructInfo_t *si; - - if (sl->count == sl->capacity) { - /* Reallocate to increase capacity */ - sl->capacity *= 2; /* Double the capacity */ - sl->items = (StructInfo_t *)realloc(sl->items, - sl->capacity * sizeof(StructInfo_t)); - for (size_t i = sl->count; i < sl->capacity; i++) - struct_info_init(&sl->items[i]); - } - si = &sl->items[sl->count++]; - si->name = steal ? struct_name : safe_strdup(struct_name); - return si; -} - -/** - * @brief Test if a StructList_t contains no structs. - * - * @param sl: Pointer to StructList_t. - * - * @return true if no items present, false otherwise. - */ -#define STRUCT_LIST_EMPTY(sl) ((sl)->count == 0) - -/** - * @brief Parse C structs from a header text and populate StructList_t. - * - * Uses compiled regexes in @conf to find struct definitions in @text, - * extracts member declarations and populates @sl with StructInfo_t - * entries. Honors include/exclude lists in @conf. - * - * The function: - * - Clears @sl before parsing. - * - Iterates all regex matches for "struct ... { ... };" as defined by - * conf->re.re_struct. - * - Removes C comments and inline comments and tokenizes the struct - * body by newline to parse individual member declarations. - * - Supports char arrays and pointer-to-char members specially. - * - * @param sl: Pointer to StructList_t to populate (must be initialized). - * @param text: Null-terminated text containing header content. - * @param conf: Pointer to configuration containing regexes and lists. - */ -static void struct_list_parse(StructList_t *sl, const char *text, Conf_t *conf) -{ - regmatch_t regmatch_struct[4]; - const char *cursor; - int rc; - - struct_list_clear(sl); - - for (cursor = text, - rc = regexec(&conf->re.re_struct, cursor, 4, regmatch_struct, 0); - rc == 0; - cursor += regmatch_struct[0].rm_eo, - rc = regexec(&conf->re.re_struct, cursor, 4, regmatch_struct, 0)) { - struct StructInfo *si; - char *struct_name; - char *body; - char *line; - - struct_name = regmatch_strdup(cursor, ®match_struct[1]); - - if (is_excluded(&conf->excl_list, struct_name, NULL) || - !is_included(&conf->incl_list, struct_name)) { - free(struct_name); - continue; - } - - si = struct_list_struct_add(sl, struct_name, true); - - body = regmatch_strdup(cursor, ®match_struct[2]); - mask_c_comments(body); - - /* Split struct body into lines */ - for (line = strtok(body, "\n"); line != NULL; line = strtok(NULL, "\n")) { - regmatch_t regmatch_member[5]; - char *trimmed_line; - - trimmed_line = trim(trim_inline_comments(line)); - - if (trimmed_line[0] == '\0' || /* empty line */ - !strchr(trimmed_line, ';') || /* skip lines w/o semicolon */ - strstr(trimmed_line, "static") || /* skip static members */ - strstr(trimmed_line, "struct")) /* skip struct members */ - continue; - - /* Look for char arrays. E.g. char buffer[10] */ - if (regexec(&conf->re.re_char_array, trimmed_line, 5, - regmatch_member, 0) == 0) { - char *name; - Member_t *m; - - name = regmatch_strdup(trimmed_line, ®match_member[2]); - if (is_excluded(&conf->excl_list, struct_name, name)) { - free(name); - continue; - } - m = struct_info_member_add(si, "const char *", name, false, true); - - m->is_char_array = true; - m->is_const = regmatch_startswith(trimmed_line, - ®match_member[1], "const"); - m->array_size = regmatch_strdup(trimmed_line, ®match_member[3]); - - continue; - } - - /* All other members */ - if (regexec(&conf->re.re_member, trimmed_line, 5, - regmatch_member, 0) == 0) { - Member_t *m; - bool is_ptr; - char *type; - char *name; - - name = regmatch_strdup(trimmed_line, ®match_member[4]); - if (is_excluded(&conf->excl_list, struct_name, name)) { - free(name); - continue; - } - - is_ptr = regmatch_contains_char(trimmed_line, - ®match_member[3], '*'); - if (is_ptr) { - bool is_char = - regmatch_startswith(trimmed_line, - ®match_member[2], "char"); - - /* Skip if we have a pointer, but it's not a "char *" */ - if (!is_char) { - free(name); - continue; - } - - /* type is used as the getter return type */ - type = safe_strdup("const char *"); - } else { - type = regmatch_strdup(trimmed_line, ®match_member[2]); - } - - m = struct_info_member_add(si, type, name, true, true); - m->is_const = regmatch_startswith(trimmed_line, - ®match_member[1], "const"); - } - } - free(body); - - if (conf->verbose && !STRUCT_INFO_EMPTY(si)) - printf("Found struct: %s (%lu members)\n", si->name, si->count); - } -} - -/** - * @brief Iterate over a StructList_t returning next StructInfo_t*. - * - * @param sl: Pointer to the struct list. - * @param si: Pointer that gets updated with StructInfo_t* at - * current index or NULL if end reached. - */ -#define STRUCT_LIST_FOREACH(sl, si) \ - for (size_t __si_next = 0; \ - ({ \ - (si) = (__si_next >= (sl)->count) \ - ? NULL : &(sl)->items[__si_next++]; \ - (si) != NULL; \ - });) - -/******************************************************************************/ - -/* - * The emit_hdr_*() and emit_src_*() helpers below generate the content of - * accessors.h and accessors.c respectively. The generated files are checked - * into the repository and must pass checkpatch.pl cleanly, so several - * design choices exist purely for that reason: - * - * 1. type_sep() — checkpatch requires that a '*' in a pointer return type - * is attached to the function name, not to the type keyword. For example: - * const char *nvme_ctrl_get_name(...) <- correct - * const char * nvme_ctrl_get_name(...) <- checkpatch error - * type_sep() returns "" when the type already ends with '*' so that no - * extra space is inserted between the type and the function name. - * - * 2. snprintf(NULL, 0, ...) — checkpatch warns about source lines longer - * than 80 characters. Because the length of a generated prototype depends - * on the struct and member names (which vary widely), we cannot know at - * compile time whether a declaration will fit on one line. snprintf with - * a NULL buffer and size 0 is a standard C99 idiom that returns the number - * of characters that *would* have been written, allowing us to measure the - * line length before committing to either the single-line or the wrapped - * two-line form. The LINE_FITS_80() macro below wraps this idiom into a - * readable boolean test. - */ - -/* - * True when the printf-formatted string would fit within checkpatch's - * 80-character line-length limit. Uses snprintf(NULL, 0, ...) to measure - * the output length without allocating a buffer (C99 - 7.19.6.5). - * - * LINE_FITS_80_NTABS is a tab-aware variant for body lines that begin with - * n hard tabs: checkpatch expands each tab to an 8-space tab stop, so each - * tab costs 7 extra visible columns compared to the 1 byte snprintf counts. - */ -#define LINE_FITS_80(fmt, ...) \ - (snprintf(NULL, 0, fmt, ##__VA_ARGS__) <= 80) -#define LINE_FITS_80_NTABS(n, fmt, ...) \ - (snprintf(NULL, 0, fmt, ##__VA_ARGS__) + (n) * 7 <= 80) - -/** - * @brief Return the separator to place between a C type and a function name. - * - * When @type already ends with '*' (e.g. "const char *") no additional - * space is needed before the function name; otherwise a single space is - * required to separate the type keyword from the identifier. - * - * @param type: The return-type string (e.g. "int", "const char *"). - * - * @return "" if @type ends with '*', " " otherwise. - */ -static const char *type_sep(const char *type) -{ - size_t len = strlen(type); - - return (len > 0 && type[len - 1] == '*') ? "" : " "; -} - -/** - * @brief Emit a header declaration for a string setter. - * - * Writes the Kerneldoc comment and prototype for a setter that accepts - * a "const char *" argument. Handles both dynamic strings (strdup'd) - * and fixed-size char arrays (snprintf'd). The prototype is wrapped - * onto two lines when it would exceed 80 characters. - * - * @param f: Output FILE*. - * @param prefix: Function-name prefix (e.g. "nvme"). - * @param sname: Struct name (e.g. "ctrl"). - * @param mname: Member name (e.g. "firmware"). - * @param is_dyn_str: true for dynamic "const char *", false for char array. - */ -static void emit_hdr_setter_str(FILE *f, const char *prefix, - const char *sname, const char *mname, bool is_dyn_str) -{ - fprintf(f, - "/**\n" - " * %s" SET_FMT "() - Set %s.\n" - " * @p: The &struct %s instance to update.\n", - prefix, sname, mname, - mname, - sname); - if (is_dyn_str) - fprintf(f, - " * @%s: New string; a copy is stored. Pass NULL to clear.\n", - mname); - else - fprintf(f, - " * @%s: New string; truncated to fit, always NUL-terminated.\n", - mname); - fprintf(f, " */\n"); - - if (LINE_FITS_80("void %s" SET_FMT "(struct %s *p, const char *%s);", - prefix, sname, mname, sname, mname)) - fprintf(f, - "void %s" SET_FMT "(struct %s *p, const char *%s);\n\n", - prefix, sname, mname, sname, mname); - else - fprintf(f, - "void %s" SET_FMT "(\n" - "\t\tstruct %s *p,\n" - "\t\tconst char *%s);\n\n", - prefix, sname, mname, sname, mname); -} - -/** - * @brief Emit a header declaration for a value setter. - * - * Writes the Kerneldoc comment and prototype for a setter that accepts - * a numeric or boolean argument. The prototype is wrapped onto two - * lines when it would exceed 80 characters. - * - * @param f: Output FILE*. - * @param prefix: Function-name prefix (e.g. "nvme"). - * @param sname: Struct name (e.g. "ctrl"). - * @param mname: Member name (e.g. "nsid"). - * @param mtype: Member type string (e.g. "__u32"). - */ -static void emit_hdr_setter_val(FILE *f, const char *prefix, - const char *sname, const char *mname, const char *mtype) -{ - fprintf(f, - "/**\n" - " * %s" SET_FMT "() - Set %s.\n" - " * @p: The &struct %s instance to update.\n" - " * @%s: Value to assign to the %s field.\n" - " */\n", - prefix, sname, mname, - mname, - sname, - mname, mname); - - if (LINE_FITS_80("void %s" SET_FMT "(struct %s *p, %s %s);", - prefix, sname, mname, sname, mtype, mname)) - fprintf(f, - "void %s" SET_FMT "(struct %s *p, %s %s);\n\n", - prefix, sname, mname, sname, mtype, mname); - else - fprintf(f, - "void %s" SET_FMT "(\n" - "\t\tstruct %s *p,\n" - "\t\t%s %s);\n\n", - prefix, sname, mname, sname, mtype, mname); -} - -/** - * @brief Emit a header declaration for a getter. - * - * Writes the Kerneldoc comment and prototype for a getter. The - * prototype is wrapped onto two lines when it would exceed 80 - * characters. - * - * @param f: Output FILE*. - * @param prefix: Function-name prefix (e.g. "nvme"). - * @param sname: Struct name (e.g. "ctrl"). - * @param mname: Member name (e.g. "firmware"). - * @param mtype: Return type string (e.g. "const char *"). - * @param is_dyn_str: true for dynamic strings (affects Return: doc). - */ -static void emit_hdr_getter(FILE *f, const char *prefix, - const char *sname, const char *mname, - const char *mtype, bool is_dyn_str) -{ - fprintf(f, - "/**\n" - " * %s" GET_FMT "() - Get %s.\n" - " * @p: The &struct %s instance to query.\n" - " *\n" - " * Return: The value of the %s field%s\n" - " */\n", - prefix, sname, mname, - mname, - sname, - mname, - is_dyn_str ? ", or NULL if not set." : "."); - - if (LINE_FITS_80("%s%s%s" GET_FMT "(const struct %s *p);", - mtype, type_sep(mtype), prefix, sname, mname, sname)) - fprintf(f, - "%s%s%s" GET_FMT "(const struct %s *p);\n\n", - mtype, type_sep(mtype), prefix, sname, mname, sname); - else - fprintf(f, - "%s%s%s" GET_FMT "(\n" - "\t\tconst struct %s *p);\n\n", - mtype, type_sep(mtype), prefix, sname, mname, sname); -} - -/** - * @brief Generate header (.h) declarations for accessors of one struct. - * - * Iterates over the members of @si and calls the appropriate emit_hdr_* - * helper for each setter and getter. - * - * @param generated_hdr: FILE* to write header declarations to. - * @param si: Pointer to StructInfo_t describing the struct and members. - * @param conf: Pointer to Conf_t containing args and generation options. - */ -static void generate_hdr(FILE *generated_hdr, StructInfo_t *si, Conf_t *conf) -{ - for (size_t m = 0; m < si->count; m++) { - Member_t *members = &si->members[m]; - bool is_dyn_str = !members->is_char_array && - streq(members->type, "const char *"); - - if (!members->is_const) { /* No setter on const members */ - if (members->is_char_array || is_dyn_str) - emit_hdr_setter_str(generated_hdr, conf->prefix, - si->name, members->name, - is_dyn_str); - else - emit_hdr_setter_val(generated_hdr, conf->prefix, - si->name, members->name, - members->type); - } - - emit_hdr_getter(generated_hdr, conf->prefix, - si->name, members->name, - members->type, is_dyn_str); - } -} - -/** - * @brief Emit a source definition for a dynamic-string setter. - * - * Generates a setter that frees the old value and strdup's the new one. - * Passing NULL clears the field. - * - * @param f: Output FILE*. - * @param prefix: Function-name prefix (e.g. "nvme"). - * @param sname: Struct name. - * @param mname: Member name. - */ -static void emit_src_setter_dynstr(FILE *f, const char *prefix, - const char *sname, const char *mname) -{ - /* Emit function signature, wrapping if it exceeds 80 chars. */ - if (LINE_FITS_80("void %s" SET_FMT "(struct %s *p, const char *%s)", - prefix, sname, mname, sname, mname)) - fprintf(f, - "void %s" SET_FMT "(struct %s *p, const char *%s)\n", - prefix, sname, mname, sname, mname); - else - fprintf(f, - "void %s" SET_FMT "(\n" - "\t\tstruct %s *p,\n" - "\t\tconst char *%s)\n", - prefix, sname, mname, sname, mname); - - /* Emit function body; wrap the strdup line if it exceeds 80 chars. - * checkpatch expands the leading tab to 8 spaces, so use NTABS(1). - */ - fprintf(f, "{\n\tfree(p->%s);\n", mname); - if (LINE_FITS_80_NTABS(1, "\tp->%s = %s ? strdup(%s) : NULL;", - mname, mname, mname)) - fprintf(f, "\tp->%s = %s ? strdup(%s) : NULL;\n", - mname, mname, mname); - else - fprintf(f, "\tp->%s =\n\t\t%s ? strdup(%s) : NULL;\n", - mname, mname, mname); - fprintf(f, "}\n\n"); -} - -/** - * @brief Emit a source definition for a fixed char-array setter. - * - * Generates a setter that uses snprintf to copy into a fixed-size - * char array, always NUL-terminating the result. @array_size may be - * a numeric literal or a symbolic constant. - * - * @param f: Output FILE*. - * @param prefix: Function-name prefix (e.g. "nvme"). - * @param sname: Struct name. - * @param mname: Member name. - * @param array_size: Size of the char array as a string. - */ -static void emit_src_setter_chararray(FILE *f, const char *prefix, - const char *sname, const char *mname, const char *array_size) -{ - if (LINE_FITS_80("void %s" SET_FMT "(struct %s *p, const char *%s)", - prefix, sname, mname, sname, mname)) - fprintf(f, - "void %s" SET_FMT "(struct %s *p, const char *%s)\n", - prefix, sname, mname, sname, mname); - else - fprintf(f, - "void %s" SET_FMT "(\n" - "\t\tstruct %s *p,\n" - "\t\tconst char *%s)\n", - prefix, sname, mname, sname, mname); - - if (str_is_all_numbers(array_size)) { - unsigned long sz = strtoul(array_size, NULL, 10); - - fprintf(f, - "{\n" - "\tsnprintf(p->%s, %lu, \"%%s\", %s);\n" - "}\n\n", - mname, sz, mname); - } else { - fprintf(f, - "{\n" - "\tsnprintf(p->%s, %s, \"%%s\", %s);\n" - "}\n\n", - mname, array_size, mname); - } -} - -/** - * @brief Emit a source definition for a value setter. - * - * Generates a setter that directly assigns the argument to the member. - * - * @param f: Output FILE*. - * @param prefix: Function-name prefix (e.g. "nvme"). - * @param sname: Struct name. - * @param mname: Member name. - * @param mtype: Member type string (e.g. "__u32"). - */ -static void emit_src_setter_val(FILE *f, const char *prefix, - const char *sname, const char *mname, const char *mtype) -{ - if (LINE_FITS_80("void %s" SET_FMT "(struct %s *p, %s %s)", - prefix, sname, mname, sname, mtype, mname)) - fprintf(f, - "void %s" SET_FMT "(struct %s *p, %s %s)\n" - "{\n" - "\tp->%s = %s;\n" - "}\n\n", - prefix, sname, mname, - sname, mtype, mname, - mname, mname); - else - fprintf(f, - "void %s" SET_FMT "(\n" - "\t\tstruct %s *p,\n" - "\t\t%s %s)\n" - "{\n" - "\tp->%s = %s;\n" - "}\n\n", - prefix, sname, mname, - sname, mtype, mname, - mname, mname); -} - -/** - * @brief Emit a source definition for a getter. - * - * Generates a getter that returns the value of the struct member. - * - * @param f: Output FILE*. - * @param prefix: Function-name prefix (e.g. "nvme"). - * @param sname: Struct name. - * @param mname: Member name. - * @param mtype: Return type string (e.g. "const char *"). - */ -static void emit_src_getter(FILE *f, const char *prefix, - const char *sname, const char *mname, const char *mtype) -{ - if (LINE_FITS_80("%s%s%s" GET_FMT "(const struct %s *p)", - mtype, type_sep(mtype), prefix, sname, mname, sname)) - fprintf(f, - "%s%s%s" GET_FMT "(const struct %s *p)\n" - "{\n" - "\treturn p->%s;\n" - "}\n\n", - mtype, type_sep(mtype), prefix, sname, mname, - sname, mname); - else - fprintf(f, - "%s%s%s" GET_FMT "(\n" - "\t\tconst struct %s *p)\n" - "{\n" - "\treturn p->%s;\n" - "}\n\n", - mtype, type_sep(mtype), prefix, sname, mname, - sname, mname); -} - -/** - * @brief Generate source (.c) implementations for accessors of one struct. - * - * Iterates over the members of @si and calls the appropriate emit_src_* - * helper for each setter and getter. - * - * @param generated_src: FILE* to write implementations to. - * @param si: Pointer to the struct description. - * @param conf: Pointer to Conf_t containing args and generation options. - */ -static void generate_src(FILE *generated_src, StructInfo_t *si, Conf_t *conf) -{ - for (size_t m = 0; m < si->count; m++) { - Member_t *member = &si->members[m]; - - if (!member->is_const) { - if (!member->is_char_array && - streq(member->type, "const char *")) - emit_src_setter_dynstr(generated_src, - conf->prefix, - si->name, member->name); - else if (member->is_char_array) - emit_src_setter_chararray(generated_src, - conf->prefix, - si->name, member->name, - member->array_size); - else - emit_src_setter_val(generated_src, conf->prefix, - si->name, member->name, - member->type); - } - - emit_src_getter(generated_src, conf->prefix, - si->name, member->name, member->type); - } -} - -/** - * @brief Generate linker script (.ld) implementations for accessors of - * one struct. - * - * Writes linker entries for each member in @si to the provided output - * FILE (@generated_ld). - * - * @param generated_ld: FILE* to write implementations to. - * @param si: Pointer to the struct description. - * @param conf: Pointer to Conf_t containing args and generation options. - */ -static void generate_ld(FILE *generated_ld, StructInfo_t *si, Conf_t *conf) -{ - for (size_t m = 0; m < si->count; m++) { - Member_t *member = &si->members[m]; - - fprintf(generated_ld, - "\t\t%s" GET_FMT ";\n" - "\t\t%s" SET_FMT ";\n", - conf->prefix, si->name, member->name, - conf->prefix, si->name, member->name); - } -} - - -/******************************************************************************/ - - -/** - * @brief Print usage information for this program. - * - * @param prog: Program name (argv[0]) used in the usage message. - */ -static void print_usage(const char *prog) -{ - printf( - "Usage: %s [options] \n" - "Options:\n" - " -c, --c-out Name of the generated *.c file. Default: %s\n" - " -h, --h-out Name of the generated *.h file. Default: %s\n" - " -l, --ld-out Name of the generated *.ld file. Default: %s\n" - " -e, --excl Exclusion list. struct::member to exclude (1 struct::member per line). Default: do not exclude anything\n" - " -i, --incl Inclusion list. structs to include (1 struct name per line). Default: include every struct found\n" - " -p, --prefix Prefix for generated function names\n" - " -v, --verbose Verbose output\n" - " -H, --help Show this message\n", - prog, - OUTPUT_FNAME_DEFAULT_C, - OUTPUT_FNAME_DEFAULT_H, - OUTPUT_FNAME_DEFAULT_LD - ); -} - -/** - * @brief Parse command line arguments and populate a Conf_t. - * - * Uses getopt_long to process supported options and expands file - * wildcards using glob(). Populates args->hdr_files with matched - * header filenames. Initializes inclusion and exclusion lists - * from --incl and --excl options. - * - * @param args: Pointer to Conf_t to initialize. - * @param argc: Argument count from main(). - * @param argv: Argument vector from main(). - * - * @note This function exits the process on fatal errors (missing files). - */ -static void args_parse(Conf_t *conf, int argc, char *argv[]) -{ - const char *opt_excl_file = NULL; - const char *opt_incl_file = NULL; - static const char *optstr = "o:c:h:l:e:i:p:vH"; - static struct option longopts[] = { - { "c-out", required_argument, NULL, 'c' }, - { "h-out", required_argument, NULL, 'h' }, - { "ld-out", required_argument, NULL, 'l' }, - { "excl", required_argument, NULL, 'e' }, - { "incl", required_argument, NULL, 'i' }, - { "prefix", required_argument, NULL, 'p' }, - { "verbose", no_argument, NULL, 'v' }, - { "help", no_argument, NULL, 'H' }, - { NULL, 0, NULL, 0 } - }; - - conf->verbose = false; - conf->c_fname = OUTPUT_FNAME_DEFAULT_C; - conf->h_fname = OUTPUT_FNAME_DEFAULT_H; - conf->l_fname = OUTPUT_FNAME_DEFAULT_LD; - conf->prefix = ""; - - for (int opt = getopt_long(argc, argv, optstr, longopts, NULL); - opt != -1; - opt = getopt_long(argc, argv, optstr, longopts, NULL)) { - switch (opt) { - case 'c': conf->c_fname = optarg; break; - case 'h': conf->h_fname = optarg; break; - case 'l': conf->l_fname = optarg; break; - case 'e': opt_excl_file = optarg; break; - case 'i': opt_incl_file = optarg; break; - case 'p': conf->prefix = optarg; break; - case 'v': conf->verbose = true; break; - case 'H': print_usage(argv[0]); exit(EXIT_SUCCESS); - default: print_usage(argv[0]); exit(EXIT_FAILURE); - } - } - - /* Remaining arguments after options are file names or wildcards */ - if (optind >= argc) { - fprintf(stderr, "Please specify header file(s) to parse\n"); - exit(EXIT_FAILURE); - } - - strlst_init(&conf->hdr_files, 16); - for (int i = optind; i < argc; ++i) { - glob_t glob_result = { 0 }; - int ret = glob(argv[i], GLOB_TILDE|GLOB_NOCHECK, NULL, - &glob_result); - - if (ret == 0) { - char *rp; - - for (size_t j = 0; j < glob_result.gl_pathc; ++j) { - rp = realpath(glob_result.gl_pathv[j], NULL); - strlst_add(&conf->hdr_files, rp, true); - } - } else { - fprintf(stderr, "Warning: No match for %s\n", argv[i]); - } - globfree(&glob_result); - } - - strlst_init(&conf->incl_list, 16); - strlst_load(&conf->incl_list, opt_incl_file); - - strlst_init(&conf->excl_list, 16); - strlst_load(&conf->excl_list, opt_excl_file); -} - -/** - * @brief Initialize Conf_t including regex compilation and loading lists. - * - * Parses command-line args via args_parse and compiles the regular - * expressions used to parse structs and members. - * - * @param conf: Pointer to Conf_t to initialize. - * @param argc: Argument count from main(). - * @param argv: Argument vector from main(). - */ -static void conf_init(Conf_t *conf, int argc, char *argv[]) -{ - args_parse(conf, argc, argv); - - regcomp(&conf->re.re_struct, STRUCT_RE, REG_EXTENDED); - regcomp(&conf->re.re_member, MEMBER_RE, REG_EXTENDED); - regcomp(&conf->re.re_char_array, CHAR_ARRAY_RE, REG_EXTENDED); -} - -/** - * @brief Free resources allocated in Conf_t. - * - * Frees include/exclude lists, releases compiled regexes and frees - * argument-held resources via args_free. - * - * @param conf: Pointer to Conf_t to free. - */ -static void conf_free(Conf_t *conf) -{ - strlst_free(&conf->hdr_files); - strlst_free(&conf->incl_list); - strlst_free(&conf->excl_list); - - regfree(&conf->re.re_char_array); - regfree(&conf->re.re_member); - regfree(&conf->re.re_struct); -} - -/** - * @brief Appends the contents of one file to another. - * - * This function copies all data from the source file stream (`srce`) - * to the destination file stream (`dest`). - * - * If `NVME_HAVE_SENDFILE` is defined, the function uses the `sendfile()` - * system call for efficient data transfer between file descriptors. Otherwise, - * it falls back to a standard character-by-character copy using `fgetc()` and - * `fputc()`. - * - * The source file position is reset to the beginning before copying. - * The destination file is not closed or flushed beyond the initial `fflush()` - * performed internally. - * - * @param dest: Destination file stream to which data will be appended. - * @param srce: Source file stream whose contents will be copied. - * - * @note Both file streams must be opened before calling this function. - * The function assumes that `dest` is writable and `srce` is readable. - * On systems that support `sendfile()`, both files must also have valid - * file descriptors. - * - * @warning This function does not perform any error checking. - * If you require robust handling of I/O errors, you should modify - * the implementation accordingly. - */ -static void append_file(FILE *dest, FILE *srce) -{ -#ifdef NVME_HAVE_SENDFILE - /* Quick file copy using Linux's sendfile system-call */ - off_t offset; - long bytes_to_copy; - - /* Make sure any buffered data is written to the files before - * copying. This is to avoid having missing data from @srce or - * data being written out of sequence in @dest. - */ - fflush(dest); - fflush(srce); - - /* Evaluate the size of the @srce file */ - fseek(srce, 0, SEEK_END); - bytes_to_copy = ftell(srce); - rewind(srce); - - offset = 0; - sendfile(fileno(dest), fileno(srce), &offset, bytes_to_copy); -#else - /* Copy character-by-character */ - int c; - - rewind(srce); - while ((c = fgetc(srce)) != EOF) - fputc(c, dest); -#endif -} - -/******************************************************************************/ - - -/** - * @brief Main program entry point. - * - * Parses CLI, reads header files, discovers structs and members, and - * generates accessor header/source files. - * - * @param argc: Argument count. - * @param argv: Argument vector. - * - * @return EXIT_SUCCESS on success (exits the program), or exits with - * failure codes on errors encountered. - */ -int main(int argc, char *argv[]) -{ - StructList_t sl; - StringList_t files_to_include; - StringList_t forward_declares; - const char *struct_to_declare; - const char *include_fname; - const char *in_hdr; - char *guard; - FILE *generated_hdr = NULL; - FILE *generated_src = NULL; - FILE *generated_ld = NULL; - FILE *tmp_hdr_code = NULL; - FILE *tmp_src_code = NULL; - FILE *tmp_ld_map = NULL; - Conf_t conf; - int dont_care; - - (void)dont_care; - - conf_init(&conf, argc, argv); - - struct_list_init(&sl); - strlst_init(&files_to_include, 0); - strlst_init(&forward_declares, 0); - - /* Creates temporary files to hold the generated code. */ - tmp_hdr_code = tmpfile(); - tmp_src_code = tmpfile(); - tmp_ld_map = tmpfile(); - - STRLST_FOREACH(&conf.hdr_files, in_hdr) { - StructInfo_t *si; - const char *in_hdr_fname = get_filename(in_hdr); - - if (conf.verbose) - printf("\nProcessing %s\n", in_hdr); - - char *text = read_file(in_hdr); - - struct_list_parse(&sl, text, &conf); - free(text); - - if (STRUCT_LIST_EMPTY(&sl)) { - if (conf.verbose) { - if (STRLST_EMPTY(&conf.incl_list)) - printf("No structs found in %s.\n", - in_hdr); - else - printf("Structs in %s are not in the include list.\n", - in_hdr); - } - continue; - } - - strlst_add(&files_to_include, in_hdr_fname, false); - - STRUCT_LIST_FOREACH(&sl, si) { - if (STRUCT_INFO_EMPTY(si)) - continue; - - strlst_add(&forward_declares, si->name, false); - - /* Generate code for the header file (*.h) */ - fprintf(tmp_hdr_code, - "/****************************************************************************\n" - " * Accessors for: struct %s\n" - " ****************************************************************************/\n" - "\n", si->name); - generate_hdr(tmp_hdr_code, si, &conf); - - /* Generate code for the source file (*.c) */ - fprintf(tmp_src_code, - "/****************************************************************************\n" - " * Accessors for: struct %s\n" - " ****************************************************************************/\n" - "\n", si->name); - generate_src(tmp_src_code, si, &conf); - - /* Generate entries for the linker script (*.ld) */ - generate_ld(tmp_ld_map, si, &conf); - } - } - - struct_list_free(&sl); - - /* We've collected all the data we needed. Now let's generate files. */ - - /*********************************************************************** - * First, output the generated header file. - */ - - /* Add a guard in the generated header file that is made of - * the UPPERCASE file name's stem. In other words, if the file - * name is "accessors.h" then the guard should be "_ACCESSORS_H_" - */ - dont_care = asprintf(&guard, "_%s_", get_filename(conf.h_fname)); - sanitize_identifier(to_uppercase(guard)); - - mkdir_fullpath(conf.h_fname, 0755); /* create output folder if needed */ - - generated_hdr = fopen(conf.h_fname, "w"); - fprintf(generated_hdr, - "%s\n" - "\n" - "%s\n" - "#ifndef %s\n" - "#define %s\n" - "\n" - "#include \n" - "#include \n" - "#include \n" - "#include \n" - "#include /* __u32, __u64, etc. */\n" - "\n", spdx_h, banner, guard, guard); - - fprintf(generated_hdr, - "/* Forward declarations. These are internal (opaque) structs. */\n"); - STRLST_FOREACH(&forward_declares, struct_to_declare) - fprintf(generated_hdr, "struct %s;\n", struct_to_declare); - strlst_free(&forward_declares); - fprintf(generated_hdr, "\n"); - - /* Copy temporary file to output */ - append_file(generated_hdr, tmp_hdr_code); - fclose(tmp_hdr_code); - fprintf(generated_hdr, "#endif /* %s */\n", guard); - fclose(generated_hdr); - free(guard); - - - /*********************************************************************** - * Second, output the generated source file. - */ - - mkdir_fullpath(conf.c_fname, 0755); /* create output folder if needed */ - generated_src = fopen(conf.c_fname, "w"); - fprintf(generated_src, - "%s\n" - "\n" - "%s\n" - "#include \n" - "#include \n" - "#include \"%s\"\n" - "\n", spdx_c, banner, get_filename(conf.h_fname)); - - STRLST_FOREACH(&files_to_include, include_fname) - fprintf(generated_src, "#include \"%s\"\n", include_fname); - strlst_free(&files_to_include); - fprintf(generated_src, "\n"); - - /* Copy temporary file to output */ - append_file(generated_src, tmp_src_code); - fclose(tmp_src_code); - fclose(generated_src); - - /*********************************************************************** - * Third, output the linker script file. - */ - mkdir_fullpath(conf.l_fname, 0755); /* create output folder if needed */ - generated_ld = fopen(conf.l_fname, "w"); - fprintf(generated_ld, - "%s\n" - "\n" - "%s\n" - "\n" - "LIBNVME_ACCESSORS_3 {\n" - " global:\n", - spdx_ld, banner); - - /* Copy temporary file to output */ - append_file(generated_ld, tmp_ld_map); - - fprintf(generated_ld, "};\n"); - fclose(tmp_ld_map); - fclose(generated_ld); - - if (conf.verbose) - printf("\nGenerated %s and %s\n", conf.h_fname, conf.c_fname); - - conf_free(&conf); - - exit(EXIT_SUCCESS); -} - diff --git a/libnvme/tools/generator/generate-accessors.md b/libnvme/tools/generator/generate-accessors.md index 5286a6edf7..42702d9a12 100644 --- a/libnvme/tools/generator/generate-accessors.md +++ b/libnvme/tools/generator/generate-accessors.md @@ -1,296 +1,271 @@ # Generate Accessors Tool -This tool generates **setter and getter functions** for C structs automatically. - It supports dynamic strings, fixed-size char arrays, const fields, and exclusion/inclusion lists. - ------- - -## Compilation / Testing - -```bash -make -make test -``` +This tool generates **setter and getter functions** for C structs automatically. It supports dynamic strings, fixed-size char arrays, and `const` fields, with control over which structs and members participate via **in-source annotations**. ------ ## Usage ``` -./generate-accessors [options] +python3 generate-accessors.py [options] ``` **Options:** -| Short | Long | Argument | Description | -| ----- | ----------- | -------- | ------------------------------------------------------------ | -| `-h` | `--h-out` | `` | Output: Full path (incl. directories) of the *.h file to generate. | -| `-c` | `--c-out` | `` | Output: Full path (incl. directories) of the *.c file to generate. | -| `-e` | `--excl` | `` | Exclusion list file with `struct::member` per line | -| `-i` | `--incl` | `` | Inclusion list file with `struct` per line. The list of `struct` to be included in the generation. When not specified, accessors will be generated for all `struct` found in the `header-file`. | -| `-p` | `--prefix` | `` | Prefix for generated function names | -| `-v` | `--verbose` | none | Verbose output showing which `struct` is being processed | -| `-H` | `--help` | none | Show this help message | +| Short | Long | Argument | Description | +| ----- | ---------- | -------- | -------------------------------------------------------- | +| `-h` | `--h-out` | `` | Full path of the `*.h` file to generate. Default: `accessors.h` | +| `-c` | `--c-out` | `` | Full path of the `*.c` file to generate. Default: `accessors.c` | +| `-l` | `--ld-out` | `` | Full path of the `*.ld` file to generate. Default: `accessors.ld` | +| `-p` | `--prefix` | `` | Prefix prepended to every generated function name | +| `-v` | `--verbose`| none | Verbose output showing which structs are being processed | +| `-H` | `--help` | none | Show this help message | ------ -## Examples - -### Single Struct Example +## Annotations -Header file `person.h`: +Struct inclusion and member behaviour are controlled by **annotations written as comments directly in the header file**. Both `/* */` (block) and `//` (line) comment styles are supported for every annotation. -``` -struct person { - char *name; - int age; - const char *id; -}; -``` +### Struct inclusion — `generate-accessors` -Command: +Place the annotation on the same line as the struct's opening brace to opt that struct in to code generation: +```c +struct nvme_ctrl { /*!generate-accessors*/ + ... +}; ``` -./generate-accessors person.h -``` - -Generated `accessors.h`: +```c +struct nvme_ctrl { //!generate-accessors + ... +}; ``` -// SPDX-License-Identifier: LGPL-2.1-or-later -/** - * This file is part of libnvme. - * - * ____ _ _ ____ _ - * / ___| ___ _ __ ___ _ __ __ _| |_ ___ __| | / ___|___ __| | ___ - * | | _ / _ \ '_ \ / _ \ '__/ _` | __/ _ \/ _` | | | / _ \ / _` |/ _ \ - * | |_| | __/ | | | __/ | | (_| | || __/ (_| | | |__| (_) | (_| | __/ - * \____|\___|_| |_|\___|_| \__,_|\__\___|\__,_| \____\___/ \__,_|\___| - * - * Auto-generated struct member accessors (setter/getter) - */ -#ifndef ACCESSORS_H -#define ACCESSORS_H +Only structs carrying this annotation will have accessors generated. All other structs in the header are ignored. -#include -#include - -#include "structs.h" - -/**************************************************************************** - * Accessors for: struct person - */ -void person_name_set(struct person *p, const char *name); -const char * person_name_get(struct person *p); +### Member exclusion — `accessors:none` -void person_age_set(struct person *p, int age); -int person_age_get(struct person *p); - -int person_id_get(struct person *p); - -#endif /* ACCESSORS_H */ +Place the annotation on a member's declaration line to suppress accessor generation for that member entirely (no setter, no getter): +```c +struct nvme_ctrl { /*!generate-accessors*/ + char *name; + char *state; //!accessors:none + char *subsysnqn; /*!accessors:none*/ +}; ``` -Generated `accessors.c`: - -``` -// SPDX-License-Identifier: LGPL-2.1-or-later -/** - * This file is part of libnvme. - * - * ____ _ _ ____ _ - * / ___| ___ _ __ ___ _ __ __ _| |_ ___ __| | / ___|___ __| | ___ - * | | _ / _ \ '_ \ / _ \ '__/ _` | __/ _ \/ _` | | | / _ \ / _` |/ _ \ - * | |_| | __/ | | | __/ | | (_| | || __/ (_| | | |__| (_) | (_| | __/ - * \____|\___|_| |_|\___|_| \__,_|\__\___|\__,_| \____\___/ \__,_|\___| - * - * Auto-generated struct member accessors (setter/getter) - */ - -#include -#include -#include "accessors.h" +### Read-only members — `accessors:readonly` -/**************************************************************************** - * Accessors for: struct person - */ -void person_name_set(struct person *p, const char *name) { - free(p->name); - p->name = name ? strdup(name) : NULL; -} +Place the annotation on a member's declaration line to generate only a getter (no setter). This has the same effect as declaring the member `const`, but without changing the type in the struct: -const char * person_name_get(struct person *p) { - return p->name; -} +```c +struct nvme_ctrl { /*!generate-accessors*/ + char *name; + char *firmware; //!accessors:readonly + char *model; /*!accessors:readonly*/ +}; +``` -void person_age_set(struct person *p, int age) { - p->age = age; -} +Members declared with the `const` qualifier are also automatically read-only. -int person_age_get(struct person *p) { - return p->age; -} +### Annotation summary -int person_id_get(struct person *p) { - return p->id; -} -``` +| Annotation | Where | Effect | +| --------------------------- | ------------ | ------------------------------- | +| `/*!generate-accessors*/` | struct brace | Include this struct | +| `//!generate-accessors` | struct brace | Include this struct | +| `/*!accessors:none*/` | member line | Skip this member entirely | +| `//!accessors:none` | member line | Skip this member entirely | +| `/*!accessors:readonly*/` | member line | Generate getter only | +| `//!accessors:readonly` | member line | Generate getter only | +| `const` qualifier on member | member type | Generate getter only (built-in) | ------ -### Multi-Struct Example +## Example -Header file `example_structs.h`: +### Header file (`person.h`) -``` -struct person { +```c +struct person { /*!generate-accessors*/ char *name; int age; - const char *id; + const char *id; /* const → getter only, no annotation needed */ + char *secret; //!accessors:none + char *role; //!accessors:readonly }; -struct car { +struct car { /*!generate-accessors*/ char *model; int year; const char *vin; }; ``` -Command: +### Command ``` -./generate-accessors --prefix my_ example_structs.h +python3 generate-accessors.py person.h ``` -Generated `accessors.h`: +### Generated `accessors.h` -``` -// SPDX-License-Identifier: LGPL-2.1-or-later -/** - * This file is part of libnvme. - * - * ____ _ _ ____ _ - * / ___| ___ _ __ ___ _ __ __ _| |_ ___ __| | / ___|___ __| | ___ - * | | _ / _ \ '_ \ / _ \ '__/ _` | __/ _ \/ _` | | | / _ \ / _` |/ _ \ - * | |_| | __/ | | | __/ | | (_| | || __/ (_| | | |__| (_) | (_| | __/ - * \____|\___|_| |_|\___|_| \__,_|\__\___|\__,_| \____\___/ \__,_|\___| - * - * Auto-generated struct member accessors (setter/getter) - */ +```c +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* ... banner ... */ -#ifndef ACCESSORS_H -#define ACCESSORS_H +#ifndef _ACCESSORS_H_ +#define _ACCESSORS_H_ #include #include +#include +#include +#include /* __u32, __u64, etc. */ -#include "example_structs.h" +/* Forward declarations. These are internal (opaque) structs. */ +struct person; +struct car; /**************************************************************************** * Accessors for: struct person + ****************************************************************************/ + +/** + * person_set_name() - Set name. + * @p: The &struct person instance to update. + * @name: New string; a copy is stored. Pass NULL to clear. */ -void my_person_name_set(struct person *p, const char *name); -const char * my_person_name_get(struct person *p); +void person_set_name(struct person *p, const char *name); -void my_person_age_set(struct person *p, int age); -int my_person_age_get(struct person *p); +/** + * person_get_name() - Get name. + * @p: The &struct person instance to query. + * + * Return: The value of the name field, or NULL if not set. + */ +const char *person_get_name(const struct person *p); -const char * my_person_id_get(struct person *p); +/** + * person_set_age() - Set age. + * @p: The &struct person instance to update. + * @age: Value to assign to the age field. + */ +void person_set_age(struct person *p, int age); +/** + * person_get_age() - Get age. + * @p: The &struct person instance to query. + * + * Return: The value of the age field. + */ +int person_get_age(const struct person *p); + +/** + * person_get_id() - Get id. + * @p: The &struct person instance to query. + * + * Return: The value of the id field, or NULL if not set. + */ +const char *person_get_id(const struct person *p); + +/* secret: no accessors (//!accessors:none) */ + +/** + * person_get_role() - Get role. + * @p: The &struct person instance to query. + * + * Return: The value of the role field, or NULL if not set. + */ +const char *person_get_role(const struct person *p); /**************************************************************************** * Accessors for: struct car - */ -void my_car_model_set(struct car *p, const char *model); -const char * my_car_model_get(struct car *p); + ****************************************************************************/ -void my_car_year_set(struct car *p, int year); -int my_car_year_get(struct car *p); +void car_set_model(struct car *p, const char *model); +const char *car_get_model(const struct car *p); -const char * my_car_vin_get(struct car *p); +void car_set_year(struct car *p, int year); +int car_get_year(const struct car *p); -#endif /* ACCESSORS_H */ +const char *car_get_vin(const struct car *p); +#endif /* _ACCESSORS_H_ */ ``` -Generated `accessors.c`: +> **Note:** The `secret` member is absent because of `//!accessors:none`. The `role` member has only a getter because of `//!accessors:readonly`. The `id` and `vin` members have only getters because they are declared `const`. -``` +### Generated `accessors.c` + +```c // SPDX-License-Identifier: LGPL-2.1-or-later -/** - * This file is part of libnvme. - * - * ____ _ _ ____ _ - * / ___| ___ _ __ ___ _ __ __ _| |_ ___ __| | / ___|___ __| | ___ - * | | _ / _ \ '_ \ / _ \ '__/ _` | __/ _ \/ _` | | | / _ \ / _` |/ _ \ - * | |_| | __/ | | | __/ | | (_| | || __/ (_| | | |__| (_) | (_| | __/ - * \____|\___|_| |_|\___|_| \__,_|\__\___|\__,_| \____\___/ \__,_|\___| - * - * Auto-generated struct member accessors (setter/getter) - */ +/* ... banner ... */ #include #include #include "accessors.h" +#include "person.h" +#include "compiler_attributes.h" + /**************************************************************************** * Accessors for: struct person - */ -void my_person_name_set(struct person *p, const char *name) { + ****************************************************************************/ + +__public void person_set_name(struct person *p, const char *name) +{ free(p->name); p->name = name ? strdup(name) : NULL; } -const char * my_person_name_get(struct person *p) { +__public const char *person_get_name(const struct person *p) +{ return p->name; } -void my_person_age_set(struct person *p, int age) { +__public void person_set_age(struct person *p, int age) +{ p->age = age; } -int my_person_age_get(struct person *p) { +__public int person_get_age(const struct person *p) +{ return p->age; } -const char * my_person_id_get(struct person *p) { +__public const char *person_get_id(const struct person *p) +{ return p->id; } -/**************************************************************************** - * Accessors for: struct car - */ -void my_car_model_set(struct car *p, const char *model) { - free(p->model); - p->model = model ? strdup(model) : NULL; +__public const char *person_get_role(const struct person *p) +{ + return p->role; } -const char * my_car_model_get(struct car *p) { - return p->model; -} +/* ... struct car accessors follow the same pattern ... */ +``` -void my_car_year_set(struct car *p, int year) { - p->year = year; -} +------ -int my_car_year_get(struct car *p) { - return p->year; -} +## Limitations -const char * my_car_vin_get(struct car *p) { - return p->vin; -} -``` +- `typedef struct` is not supported. +- Nested structs (a `struct` member whose type is also a `struct`) are skipped. +- Only `char *` pointer members are supported; other pointer types are skipped. ------ -### Notes +## Notes -1. **Dynamic strings** (`char *`) are NULL-safe. -2. **Const fields** generate **getter-only functions**. -3. Numeric fields and other types have normal setters/getters. -4. The `--prefix` option adds a custom prefix to all generated functions. -5. The exclusion list (`--excl`) prevents generating accessors for specific `struct:member` pairs. -7. The inclusion list (`--incl`) limits generation to only the listed `struct` names. \ No newline at end of file +1. **Dynamic strings** (`char *`) — setters store a `strdup()` copy; passing `NULL` clears the field. +2. **Fixed char arrays** (`char foo[N]`) — setters use `snprintf`, always NUL-terminated. +3. **`const` members** — only a getter is generated, no setter. +4. **`//!accessors:readonly`** — same effect as `const`: getter only. +5. **`//!accessors:none`** — member is completely ignored by the generator. +6. **`--prefix`** — prepended to every function name (e.g. `--prefix nvme_` turns `ctrl_set_name` into `nvme_ctrl_set_name`). +7. **Line length** — generated code is automatically wrapped to stay within the 80-column limit required by `checkpatch.pl`. diff --git a/libnvme/tools/generator/generate-accessors.py b/libnvme/tools/generator/generate-accessors.py new file mode 100755 index 0000000000..3fc159bfc2 --- /dev/null +++ b/libnvme/tools/generator/generate-accessors.py @@ -0,0 +1,672 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later +""" +generate-accessors.py — Generate setter/getter accessor functions for C structs. + +This file is part of libnvme. +Copyright (c) 2025, Dell Technologies Inc. or its subsidiaries. +Authors: Martin Belanger + +Parses C header files and produces: + accessors.h — function declarations with KernelDoc comments + accessors.c — function implementations + accessors.ld — linker version-script entries + +Limitations: + - Does not support typedef struct. + - Does not support struct within struct. + +Struct inclusion — annotate the opening brace line of the struct: + struct nvme_ctrl { /*!generate-accessors*/ + struct nvme_ctrl { //!generate-accessors + +Member exclusion — annotate the member declaration line: + char *model; /*!accessors:none*/ + char *model; //!accessors:none + +Read-only members (getter only, setter suppressed): + - Members declared with the 'const' qualifier, or + - Annotate the member declaration line: + char *state; /*!accessors:readonly*/ + char *state; //!accessors:readonly + +Example usage: + ./generate-accessors.py private.h + ./generate-accessors.py --prefix nvme_ private.h +""" + +import argparse +import glob as glob_module +import io +import os +import re +import sys + +# --------------------------------------------------------------------------- +# Output format — controls getter/setter function naming. +# {pre} = "{prefix}{struct_name}", {mem} = member name +# Alternate style: "{pre}_{mem}_set" / "{pre}_{mem}_get" +# --------------------------------------------------------------------------- +SET_FMT = "{pre}_set_{mem}" +GET_FMT = "{pre}_get_{mem}" + +SPDX_C = "// SPDX-License-Identifier: LGPL-2.1-or-later" +SPDX_H = "/* SPDX-License-Identifier: LGPL-2.1-or-later */" +SPDX_LD = "# SPDX-License-Identifier: LGPL-2.1-or-later" + +BANNER = ( + "/**\n" + " * This file is part of libnvme.\n" + " *\n" + " * Copyright (c) 2025, Dell Technologies Inc. or its subsidiaries.\n" + " * Authors: Martin Belanger \n" + " *\n" + " * ____ _ _ ____ _\n" + " * / ___| ___ _ __ ___ _ __ __ _| |_ ___ __| | / ___|___ __| | ___\n" + " * | | _ / _ \\ '_ \\ / _ \\ '__/ _` | __/ _ \\/ _` | | | / _ \\ / _` |/ _ \\\n" + " * | |_| | __/ | | | __/ | | (_| | || __/ (_| | | |__| (_) | (_| | __/\n" + " * \\____|\\___|_| |_|\\___|_| \\__,_|\\__\\___|\\__,_| \\____\\___/ \\__,_|\\___|\n" + " *\n" + " * Auto-generated struct member accessors (setter/getter)\n" + " *\n" + " * To update run: meson compile -C [BUILD-DIR] update-accessors\n" + " * Or: make update-accessors\n" + " */" +) + +# --------------------------------------------------------------------------- +# Regular expressions +# --------------------------------------------------------------------------- + +# Matches: struct name { body }; +# [^}]* matches any character except '}', including newlines. +STRUCT_RE = re.compile( + r'struct\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([^}]*)\}\s*;' +) + +# Matches: [const] char name[size]; +CHAR_ARRAY_RE = re.compile( + r'^(const\s+)?char\s+([A-Za-z_][A-Za-z0-9_]*)\s*\[\s*([A-Za-z0-9_]+)\s*\]\s*;' +) + +# Matches: [const] type[*] name; +MEMBER_RE = re.compile( + r'^(const\s+)?([A-Za-z_][A-Za-z0-9_]*)([*\s]+)([A-Za-z_][A-Za-z0-9_]*)\s*;' +) + + +# --------------------------------------------------------------------------- +# Annotation helpers +# --------------------------------------------------------------------------- + +def has_annotation(text, annotation): + """Return True if *text* contains /*!annotation*/ or //!annotation.""" + return f'/*!{annotation}*/' in text or f'//!{annotation}' in text + + +def strip_block_comments(text): + """Remove /* ... */ block comments (replaced with a single space each).""" + return re.sub(r'/\*.*?\*/', ' ', text, flags=re.DOTALL) + + +def strip_inline_comment(line): + """Remove a // comment and everything after it.""" + idx = line.find('//') + return line[:idx] if idx >= 0 else line + + +# --------------------------------------------------------------------------- +# Misc helpers +# --------------------------------------------------------------------------- + +def sanitize_identifier(s): + """Replace characters that are invalid in a C identifier with '_'.""" + if not s: + return s + chars = list(s) + if not (chars[0].isalpha() or chars[0] == '_'): + chars[0] = '_' + for i in range(1, len(chars)): + if not (chars[i].isalnum() or chars[i] == '_'): + chars[i] = '_' + return ''.join(chars) + + +def type_sep(type_str): + """Return '' when *type_str* ends with '*', else ' '. + + checkpatch.pl requires that '*' in a pointer return type is attached to + the function name, not the type keyword (e.g. ``const char *foo(...)`` + not ``const char * foo(...)``). + """ + return '' if type_str.endswith('*') else ' ' + + +def makedirs_for(filepath): + """Create all intermediate directories needed to hold *filepath*.""" + os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True) + + +# --------------------------------------------------------------------------- +# Line-length helpers +# +# checkpatch.pl enforces an 80-column limit. Because generated function +# names vary in length we must measure before committing to a layout. +# +# fits_80(s) — True when len(s) <= 80. +# fits_80_ntabs(n, s) — True when the line would be <= 80 visible columns +# given that it starts with n hard tabs (each tab +# expands to 8 spaces, costing 7 extra visible columns +# beyond the 1 byte that len() counts). +# --------------------------------------------------------------------------- + +def fits_80(s): + return len(s) <= 80 + + +def fits_80_ntabs(n, s): + return len(s) + n * 7 <= 80 + + +# --------------------------------------------------------------------------- +# Member data class +# --------------------------------------------------------------------------- + +class Member: + """Represents one member of a parsed C struct.""" + + __slots__ = ('name', 'type', 'is_const', 'is_char_array', 'array_size') + + def __init__(self, name, type_str, is_const, is_char_array, array_size): + self.name = name + self.type = type_str # e.g. "const char *", "int", "__u32" + self.is_const = is_const # True → getter only (no setter generated) + self.is_char_array = is_char_array + self.array_size = array_size # only valid when is_char_array is True + + +# --------------------------------------------------------------------------- +# Parsing +# --------------------------------------------------------------------------- + +def parse_members(struct_name, raw_body, verbose): + """Parse *raw_body* and return a list of Member objects. + + Annotations are detected on the **raw** (un-stripped) line so that + comment masking cannot hide them. Comments are stripped only afterwards, + for regex matching. + """ + members = [] + + for raw_line in raw_body.splitlines(): + # ---------------------------------------------------------------- + # Annotation checks on the raw line — BEFORE stripping comments. + # ---------------------------------------------------------------- + if has_annotation(raw_line, 'accessors:none'): + continue + readonly = has_annotation(raw_line, 'accessors:readonly') + + # ---------------------------------------------------------------- + # Strip comments for member-declaration parsing. + # ---------------------------------------------------------------- + clean = strip_inline_comment(strip_block_comments(raw_line)).strip() + + if not clean or ';' not in clean: + continue + if 'static' in clean or 'struct' in clean: + continue + + # --- char array: [const] char name[size]; ----------------------- + m = CHAR_ARRAY_RE.match(clean) + if m: + members.append(Member( + name=m.group(2), + type_str='const char *', + is_const=readonly or bool(m.group(1)), + is_char_array=True, + array_size=m.group(3), + )) + continue + + # --- general member: [const] type[*] name; ---------------------- + m = MEMBER_RE.match(clean) + if m: + is_const_qual = bool(m.group(1)) + type_base = m.group(2) + ptr_part = m.group(3) + name = m.group(4) + + is_ptr = '*' in ptr_part + if is_ptr: + if type_base != 'char': + continue # only char* pointers are supported + type_str = 'const char *' + else: + type_str = type_base + + members.append(Member( + name=name, + type_str=type_str, + is_const=readonly or is_const_qual, + is_char_array=False, + array_size=None, + )) + + return members + + +def parse_file(text, verbose): + """Return list of (struct_name, [Member]) tuples found in *text*. + + Only structs annotated with ``/*!generate-accessors*/`` or + ``//!generate-accessors`` as the first token inside the opening brace + are processed. + """ + result = [] + + for match in STRUCT_RE.finditer(text): + struct_name = match.group(1) + raw_body = match.group(2) + + # The annotation must be the first token after the opening '{'. + first_token = raw_body.lstrip() + if not (first_token.startswith('/*!generate-accessors*/') or + first_token.startswith('//!generate-accessors')): + continue + + members = parse_members(struct_name, raw_body, verbose) + + if verbose and members: + print(f"Found struct: {struct_name} ({len(members)} members)") + + if members: + result.append((struct_name, members)) + + return result + + +# --------------------------------------------------------------------------- +# Header (*.h) code emitters +# --------------------------------------------------------------------------- + +def _set_name(prefix, sname, mname): + return SET_FMT.format(pre=f'{prefix}{sname}', mem=mname) + + +def _get_name(prefix, sname, mname): + return GET_FMT.format(pre=f'{prefix}{sname}', mem=mname) + + +def emit_hdr_setter_str(f, prefix, sname, mname, is_dyn_str): + """Emit a header declaration for a string setter.""" + f.write( + f'/**\n' + f' * {_set_name(prefix, sname, mname)}() - Set {mname}.\n' + f' * @p: The &struct {sname} instance to update.\n' + ) + if is_dyn_str: + f.write( + f' * @{mname}: New string; a copy is stored. Pass NULL to clear.\n' + ) + else: + f.write( + f' * @{mname}: New string; truncated to fit, always NUL-terminated.\n' + ) + f.write(' */\n') + + single = (f'void {_set_name(prefix, sname, mname)}' + f'(struct {sname} *p, const char *{mname});') + if fits_80(single): + f.write(single + '\n\n') + else: + f.write( + f'void {_set_name(prefix, sname, mname)}(\n' + f'\t\tstruct {sname} *p,\n' + f'\t\tconst char *{mname});\n\n' + ) + + +def emit_hdr_setter_val(f, prefix, sname, mname, mtype): + """Emit a header declaration for a value setter.""" + f.write( + f'/**\n' + f' * {_set_name(prefix, sname, mname)}() - Set {mname}.\n' + f' * @p: The &struct {sname} instance to update.\n' + f' * @{mname}: Value to assign to the {mname} field.\n' + f' */\n' + ) + + single = (f'void {_set_name(prefix, sname, mname)}' + f'(struct {sname} *p, {mtype} {mname});') + if fits_80(single): + f.write(single + '\n\n') + else: + f.write( + f'void {_set_name(prefix, sname, mname)}(\n' + f'\t\tstruct {sname} *p,\n' + f'\t\t{mtype} {mname});\n\n' + ) + + +def emit_hdr_getter(f, prefix, sname, mname, mtype, is_dyn_str): + """Emit a header declaration for a getter.""" + tail = ', or NULL if not set.' if is_dyn_str else '.' + f.write( + f'/**\n' + f' * {_get_name(prefix, sname, mname)}() - Get {mname}.\n' + f' * @p: The &struct {sname} instance to query.\n' + f' *\n' + f' * Return: The value of the {mname} field{tail}\n' + f' */\n' + ) + + sep = type_sep(mtype) + single = (f'{mtype}{sep}{_get_name(prefix, sname, mname)}' + f'(const struct {sname} *p);') + if fits_80(single): + f.write(single + '\n\n') + else: + f.write( + f'{mtype}{sep}{_get_name(prefix, sname, mname)}(\n' + f'\t\tconst struct {sname} *p);\n\n' + ) + + +def generate_hdr(f, prefix, struct_name, members): + """Write header declarations for all members of one struct.""" + for member in members: + is_dyn_str = (not member.is_char_array and + member.type == 'const char *') + if not member.is_const: + if member.is_char_array or is_dyn_str: + emit_hdr_setter_str(f, prefix, struct_name, + member.name, is_dyn_str) + else: + emit_hdr_setter_val(f, prefix, struct_name, + member.name, member.type) + emit_hdr_getter(f, prefix, struct_name, + member.name, member.type, is_dyn_str) + + +# --------------------------------------------------------------------------- +# Source (*.c) code emitters +# --------------------------------------------------------------------------- + +PUB = '' # PUB = '__public ' # Uncomment when merging the __public branch + + +def emit_src_setter_dynstr(f, prefix, sname, mname): + """Emit a dynamic-string setter (free old + strdup new).""" + sig = (f'{PUB}void {_set_name(prefix, sname, mname)}' + f'(struct {sname} *p, const char *{mname})') + if fits_80(sig): + f.write(sig + '\n') + else: + f.write( + f'{PUB}void {_set_name(prefix, sname, mname)}(\n' + f'\t\tstruct {sname} *p,\n' + f'\t\tconst char *{mname})\n' + ) + + f.write(f'{{\n\tfree(p->{mname});\n') + body = f'\tp->{mname} = {mname} ? strdup({mname}) : NULL;' + if fits_80_ntabs(1, body): + f.write(body + '\n') + else: + f.write(f'\tp->{mname} =\n\t\t{mname} ? strdup({mname}) : NULL;\n') + f.write('}\n\n') + + +def emit_src_setter_chararray(f, prefix, sname, mname, array_size): + """Emit a fixed char-array setter (snprintf).""" + sig = (f'{PUB}void {_set_name(prefix, sname, mname)}' + f'(struct {sname} *p, const char *{mname})') + if fits_80(sig): + f.write(sig + '\n') + else: + f.write( + f'{PUB}void {_set_name(prefix, sname, mname)}(\n' + f'\t\tstruct {sname} *p,\n' + f'\t\tconst char *{mname})\n' + ) + + if array_size.isdigit(): + f.write( + f'{{\n\tsnprintf(p->{mname}, {int(array_size)}, "%s", {mname});\n}}\n\n' + ) + else: + f.write( + f'{{\n\tsnprintf(p->{mname}, {array_size}, "%s", {mname});\n}}\n\n' + ) + + +def emit_src_setter_val(f, prefix, sname, mname, mtype): + """Emit a value setter (direct assignment).""" + sig = (f'{PUB}void {_set_name(prefix, sname, mname)}' + f'(struct {sname} *p, {mtype} {mname})') + if fits_80(sig): + f.write( + sig + '\n' + f'{{\n\tp->{mname} = {mname};\n}}\n\n' + ) + else: + f.write( + f'{PUB}void {_set_name(prefix, sname, mname)}(\n' + f'\t\tstruct {sname} *p,\n' + f'\t\t{mtype} {mname})\n' + f'{{\n\tp->{mname} = {mname};\n}}\n\n' + ) + + +def emit_src_getter(f, prefix, sname, mname, mtype): + """Emit a getter (return member value).""" + sep = type_sep(mtype) + sig = (f'{PUB}{mtype}{sep}{_get_name(prefix, sname, mname)}' + f'(const struct {sname} *p)') + if fits_80(sig): + f.write( + sig + '\n' + f'{{\n\treturn p->{mname};\n}}\n\n' + ) + else: + f.write( + f'{PUB}{mtype}{sep}{_get_name(prefix, sname, mname)}(\n' + f'\t\tconst struct {sname} *p)\n' + f'{{\n\treturn p->{mname};\n}}\n\n' + ) + + +def generate_src(f, prefix, struct_name, members): + """Write source implementations for all members of one struct.""" + for member in members: + if not member.is_const: + is_dyn_str = (not member.is_char_array and + member.type == 'const char *') + if is_dyn_str: + emit_src_setter_dynstr(f, prefix, struct_name, member.name) + elif member.is_char_array: + emit_src_setter_chararray(f, prefix, struct_name, + member.name, member.array_size) + else: + emit_src_setter_val(f, prefix, struct_name, + member.name, member.type) + emit_src_getter(f, prefix, struct_name, member.name, member.type) + + +# --------------------------------------------------------------------------- +# Linker script (*.ld) emitter +# --------------------------------------------------------------------------- + +def generate_ld(f, prefix, struct_name, members): + """Write linker version-script entries for all members of one struct.""" + for member in members: + f.write( + f'\t\t{_get_name(prefix, struct_name, member.name)};\n' + f'\t\t{_set_name(prefix, struct_name, member.name)};\n' + ) + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description='Generate C struct accessor functions.', + add_help=False, # -h is reserved for --h-out + ) + parser.add_argument('-c', '--c-out', default='accessors.c', + dest='c_fname', metavar='FILE', + help='Generated *.c file. Default: accessors.c') + parser.add_argument('-h', '--h-out', default='accessors.h', + dest='h_fname', metavar='FILE', + help='Generated *.h file. Default: accessors.h') + parser.add_argument('-l', '--ld-out', default='accessors.ld', + dest='l_fname', metavar='FILE', + help='Generated *.ld file. Default: accessors.ld') + parser.add_argument('-p', '--prefix', default='', + dest='prefix', metavar='STR', + help='Prefix prepended to every generated function name.') + parser.add_argument('-v', '--verbose', action='store_true', + help='Verbose output.') + parser.add_argument('-H', '--help', action='help', + default=argparse.SUPPRESS, + help='Show this message and exit.') + parser.add_argument('headers', nargs='+', + help='Header files to parse (wildcards accepted).') + args = parser.parse_args() + + # Expand wildcards in the header file arguments. + header_files = [] + for pattern in args.headers: + expanded = glob_module.glob(pattern) + if expanded: + header_files.extend(os.path.realpath(p) for p in sorted(expanded)) + else: + print(f"Warning: No match for {pattern}", file=sys.stderr) + + if not header_files: + print("error: no input headers found", file=sys.stderr) + sys.exit(1) + + # ----------------------------------------------------------------------- + # Pass 1 — parse all header files, accumulate generated fragments. + # ----------------------------------------------------------------------- + files_to_include = [] # basenames of headers that contributed structs + forward_declares = [] # struct names needing forward declarations + hdr_parts = [] # fragments for accessors.h + src_parts = [] # fragments for accessors.c + ld_parts = [] # fragments for accessors.ld + + for in_hdr in header_files: + if args.verbose: + print(f"\nProcessing {in_hdr}") + + try: + with open(in_hdr) as f: + text = f.read() + except OSError as e: + print(f"error: cannot read '{in_hdr}': {e}", file=sys.stderr) + sys.exit(1) + + structs = parse_file(text, args.verbose) + + if not structs: + if args.verbose: + print(f"No annotated structs found in {in_hdr}.") + continue + + files_to_include.append(os.path.basename(in_hdr)) + + for struct_name, members in structs: + forward_declares.append(struct_name) + + section_banner = ( + f'/****************************************************************************\n' + f' * Accessors for: struct {struct_name}\n' + f' ****************************************************************************/\n' + f'\n' + ) + + hdr_buf = io.StringIO() + hdr_buf.write(section_banner) + generate_hdr(hdr_buf, args.prefix, struct_name, members) + hdr_parts.append(hdr_buf.getvalue()) + + src_buf = io.StringIO() + src_buf.write(section_banner) + generate_src(src_buf, args.prefix, struct_name, members) + src_parts.append(src_buf.getvalue()) + + ld_buf = io.StringIO() + generate_ld(ld_buf, args.prefix, struct_name, members) + ld_parts.append(ld_buf.getvalue()) + + # ----------------------------------------------------------------------- + # Pass 2 — write output files. + # ----------------------------------------------------------------------- + + # --- accessors.h ------------------------------------------------------- + guard = '_' + sanitize_identifier(os.path.basename(args.h_fname).upper()) + '_' + + makedirs_for(args.h_fname) + with open(args.h_fname, 'w') as f: + f.write( + f'{SPDX_H}\n' + f'\n' + f'{BANNER}\n' + f'#ifndef {guard}\n' + f'#define {guard}\n' + f'\n' + f'#include \n' + f'#include \n' + f'#include \n' + f'#include \n' + f'#include /* __u32, __u64, etc. */\n' + f'\n' + ) + f.write('/* Forward declarations. These are internal (opaque) structs. */\n') + for s in forward_declares: + f.write(f'struct {s};\n') + f.write('\n') + f.write(''.join(hdr_parts)) + f.write(f'#endif /* {guard} */\n') + + # --- accessors.c ------------------------------------------------------- + makedirs_for(args.c_fname) + with open(args.c_fname, 'w') as f: + f.write( + f'{SPDX_C}\n' + f'\n' + f'{BANNER}\n' + f'#include \n' + f'#include \n' + f'#include "{os.path.basename(args.h_fname)}"\n' + f'\n' + ) + for fname in files_to_include: + f.write(f'#include "{fname}"\n') + # f.write('#include "compiler_attributes.h"\n') # Uncomment when merging the __public branch + f.write('\n') + f.write(''.join(src_parts)) + + # --- accessors.ld ------------------------------------------------------ + makedirs_for(args.l_fname) + with open(args.l_fname, 'w') as f: + f.write( + f'{SPDX_LD}\n' + f'\n' + f'{BANNER}\n' + f'\n' + f'LIBNVME_ACCESSORS_3 {{\n' + f'\tglobal:\n' + ) + f.write(''.join(ld_parts)) + f.write('};\n') + + if args.verbose: + print(f"\nGenerated {args.h_fname} and {args.c_fname}") + + +if __name__ == '__main__': + main() diff --git a/libnvme/tools/generator/meson.build b/libnvme/tools/generator/meson.build index f9c1f93910..6b20e9068f 100644 --- a/libnvme/tools/generator/meson.build +++ b/libnvme/tools/generator/meson.build @@ -9,12 +9,13 @@ # pre-generated and committed to the source tree. They are NOT # regenerated during a normal build. # -# To regenerate them after changing private.h or the include/exclude lists: +# To regenerate them after changing private.h (e.g. adding or removing +# a /*!generate-accessors*/ annotation): # # meson compile -C update-accessors # --------------------------------------------------------------------------- -# Developer-only helper: build the generator and regenerate if structs change. +# Developer-only helper: run the generator and regenerate if structs change. # NOT executed during a normal build (build_by_default: false). # Usage: meson compile -C update-accessors # --------------------------------------------------------------------------- @@ -24,31 +25,16 @@ libnvme_src = libnvme_srcroot / 'src' libnvme_src_nvme = libnvme_src / 'nvme' -generate_accessors = executable( - 'generate-accessors', - [ - 'generate-accessors.c', - ], - c_args: [ - '-D_GNU_SOURCE', - ], - dependencies: [ - config_dep, - ], - native: true, - build_by_default: false, -) +_py3 = find_program('python3', required: true) run_target( 'update-accessors', command: [ 'update-accessors.sh', - generate_accessors, + _py3, + files('generate-accessors.py'), libnvme_src_nvme, libnvme_src, - meson.current_source_dir() / 'generate-accessors-include.list', - meson.current_source_dir() / 'generate-accessors-exclude.list', libnvme_src_nvme / 'private.h', ], - depends: generate_accessors, ) diff --git a/libnvme/tools/generator/update-accessors.sh b/libnvme/tools/generator/update-accessors.sh index 79bca3b49a..a21a90268b 100755 --- a/libnvme/tools/generator/update-accessors.sh +++ b/libnvme/tools/generator/update-accessors.sh @@ -19,21 +19,19 @@ # knows exactly what to change in accessors.ld. # # Arguments (supplied by the Meson run_target): -# $1 path to the compiled generate-accessors binary -# $2 source directory for accessors.c and accessors.h (src/nvme/) -# $3 source directory for accessors.ld (src/) -# $4 path to generate-accessors-include.list -# $5 path to generate-accessors-exclude.list -# $6 ... one or more input headers (wildcards are accepted) +# $1 path to the python3 interpreter +# $2 path to generate-accessors.py +# $3 source directory for accessors.c and accessors.h (src/nvme/) +# $4 source directory for accessors.ld (src/) +# $5 ... one or more input headers (wildcards are accepted) set -euo pipefail -GENERATOR="${1:?missing generator binary}" -NVME_SRCDIR="${2:?missing nvme source directory}" -LD_SRCDIR="${3:?missing ld source directory}" -INCL_FILE="${4:?missing file generate-accessors-include.list}" -EXCL_FILE="${5:?missing file generate-accessors-exclude.list}" -shift 5 +PYTHON="${1:?missing python3 interpreter}" +GENERATOR="${2:?missing generator script}" +NVME_SRCDIR="${3:?missing nvme source directory}" +LD_SRCDIR="${4:?missing ld source directory}" +shift 4 INPUT_HEADERS=("$@") [ ${#INPUT_HEADERS[@]} -gt 0 ] || { echo "error: no input headers specified" >&2; exit 1; } @@ -42,12 +40,10 @@ trap 'rm -rf "$TMPDIR"' EXIT echo "Regenerating accessor files..." -"$GENERATOR" \ +"$PYTHON" "$GENERATOR" \ --h-out "$TMPDIR/accessors.h" \ --c-out "$TMPDIR/accessors.c" \ --ld-out "$TMPDIR/accessors.ld" \ - --incl "$INCL_FILE" \ - --excl "$EXCL_FILE" \ "${INPUT_HEADERS[@]}" # ---------------------------------------------------------------------------