diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 84071d042c85643dfc181549f038e46c9c4b354a..f2398732994160a13b949f79825060221d53587f 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -145,6 +145,7 @@ typedef _Bool bool; #define API_CONFIG_GET_STRING 0x130 #define API_CONFIG_GET_INTEGER 0x131 #define API_CONFIG_GET_BOOLEAN 0x132 +#define API_CONFIG_SET_STRING 0x133 /* clang-format on */ typedef uint32_t api_int_id_t; @@ -2060,5 +2061,21 @@ API(API_CONFIG_GET_BOOLEAN, int epic_config_get_boolean(const char *key, bool *v API(API_CONFIG_GET_STRING, int epic_config_get_string(const char *key, char *buf, size_t buf_len)); +/** + * Write a string to the configuration file. + * + * :param char* key: Name of the option to write + * :param char* value: The value to write + * :return: `0` on success or a negative value if an error occured. Possivle + * errors: + * + * - ``-EINVAL``: Parameters out of range + * - ``-ENOENT``: Key already exists but can not be read + * - ``-EIO`` : Unspecified I/O error + * - Any fopen/fread/fwrite/fclose related error code + * + * .. versionadded:: 1.16 + */ +API(API_CONFIG_SET_STRING, int epic_config_set_string(const char *key, const char *value)); #endif /* _EPICARDIUM_H */ diff --git a/epicardium/modules/config.c b/epicardium/modules/config.c index 922dae657fe78be03280d5145bba651c0b6cbfd6..6874318a1e3f0bdfa58cd61dd4f81b584eaa26ff 100644 --- a/epicardium/modules/config.c +++ b/epicardium/modules/config.c @@ -11,6 +11,7 @@ #include <stdlib.h> #include <unistd.h> #include <stddef.h> +#include <stdio.h> #define MAX_LINE_LENGTH 80 #define KEYS_PER_BLOCK 16 @@ -381,3 +382,222 @@ bool config_get_boolean_with_default(const char *key, bool default_value) return value; } } + +// TODO: don't allow things like "execute_elf" to be set +int epic_config_set_string(const char *key, const char *value) +{ + config_slot *slot = find_config_slot(key); + bool present = slot && slot->value_offset; + int ret = 0; + + if (snprintf(NULL, 0, "\n%s = %s\n", key, value) > MAX_LINE_LENGTH) { + return -EINVAL; + } + + if (!present) { + /* Easy case: We simply add the new option at the + * end of the file. */ + + char buf[MAX_LINE_LENGTH]; + /* Leading new line because I'm lazy */ + ret = snprintf(buf, sizeof(buf), "\n%s = %s\n", key, value); + + if (ret < 0 || ret >= (int)sizeof(buf)) { + return -EINVAL; + } + + int fd = epic_file_open("card10.cfg", "a"); + if (fd < 0) { + LOG_DEBUG( + "card10.cfg", + "open for appending failed: %s (%d)", + strerror(-fd), + fd + ); + return fd; + } + + int write_ret = epic_file_write(fd, buf, strlen(buf)); + if (write_ret < 0) { + LOG_DEBUG( + "card10.cfg", + "writing failed: %s (%d)", + strerror(-write_ret), + write_ret + ); + } + + if (write_ret < (int)strlen(buf)) { + LOG_DEBUG( + "card10.cfg", + "writing failed to write all bytes (%d of %d)", + write_ret, + strlen(buf) + ); + } + + ret = epic_file_close(fd); + + if (ret < 0) { + LOG_DEBUG( + "card10.cfg", + "close failed: %s (%d)", + strerror(-ret), + ret + ); + } + if (write_ret < 0) { + return write_ret; + } + if (ret < 0) { + return ret; + } + if (write_ret < (int)strlen(buf)) { + LOG_DEBUG( + "card10.cfg", + "writing failed to write all bytes (%d of %d)", + write_ret, + strlen(buf) + ); + return -EIO; + } + } else { + /* Complex case: The value is already somewhere in the file. + * We do not want to lose existing formatting or comments. + * Solution: Copy parts of the file, insert new value, copy + * rest, rename. + */ + char buf[128]; + int fd1 = -1; + int fd2 = -1; + ret = epic_config_get_string(key, buf, sizeof(buf)); + + if (ret < 0) { + LOG_DEBUG( + "card10.cfg", + "could not read old value (%d)", + ret + ); + goto out; + } + + int old_len = strlen(buf); + + fd1 = epic_file_open("card10.cfg", "r"); + if (fd1 < 0) { + LOG_DEBUG( + "card10.cfg", + "open for read failed: %s (%d)", + strerror(-fd1), + fd1 + ); + ret = fd1; + goto out; + } + + fd2 = epic_file_open("card10.nfg", "w"); + if (fd2 < 0) { + LOG_DEBUG( + "card10.nfg", + "open for writing failed: %s (%d)", + strerror(-fd2), + fd2 + ); + ret = fd2; + goto out; + } + + /* Copy over slot->value_offset bytes */ + int i = slot->value_offset; + while (i > 0) { + int n = i > (int)sizeof(buf) ? (int)sizeof(buf) : i; + ret = epic_file_read(fd1, buf, n); + if (ret < 0) { + LOG_DEBUG( + "card10.cfg", + "read failed: rc: %d", + ret + ); + goto out; + } + + int ret2 = epic_file_write(fd2, buf, ret); + + if (ret2 < 0) { + ret = ret2; + LOG_DEBUG( + "card10.nfg", + "write failed: rc: %d", + ret + ); + goto out; + } + i -= ret; + } + + /* Insert new value into the new file */ + ret = epic_file_write(fd2, value, strlen(value)); + if (ret < 0) { + LOG_DEBUG("card10.nfg", "write failed: rc: %d", ret); + goto out; + } + + /* Skip the old value inside the old file */ + epic_file_seek(fd1, old_len, SEEK_CUR); + + /* Copy the rest of the old file to the new file */ + while (true) { + int ret = epic_file_read(fd1, buf, sizeof(buf)); + + if (ret == 0) { + break; + } + + if (ret < 0) { + LOG_DEBUG( + "card10.cfg", + "read failed: rc: %d", + ret + ); + goto out; + } + + int ret2 = epic_file_write(fd2, buf, ret); + + if (ret2 < 0) { + ret = ret2; + LOG_DEBUG( + "card10.nfg", + "write failed: rc: %d", + ret + ); + goto out; + } + + if (ret < (int)sizeof(buf)) { + break; + } + } + + out: + if (fd1 >= 0) { + epic_file_close(fd1); + } + if (fd2 >= 0) { + int ret2 = epic_file_close(fd2); + if (ret >= 0) { + ret = ret2; + } + } + + if (ret >= 0) { + epic_file_unlink("card10.cfg"); + epic_file_rename("card10.nfg", "card10.cfg"); + } + } + + /* Reload config so the new key or the changed value is available */ + load_config(); + + return ret < 0 ? ret : 0; +}