/* * This program source code file is part of the KTag project. * * 🛡️ 🃞 * * Copyright © 2024-2025 Joseph P. Kearney and the KTag developers. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * There should be a copy of the GNU Affero General Public License in the LICENSE * file in the root of this repository. If not, see . */ #include #include #include #include #include #define MAX_LINE_LENGTH 140 #define MAX_KEY_LENGTH 64 #define MAX_VALUE_LENGTH 64 static const char *TAG = "NVM KV"; static const char *TEMP_FILE = "/usb/esp/temp.txt"; // Trims the whitespace from both ends of a string. static void KV_Trim(char *str) { char *start = str; char *end = str + strlen(str) - 1; while (*start && (*start == ' ' || *start == '\t')) start++; while (end > start && (*end == ' ' || *end == '\t' || *end == '\n')) end--; *(end + 1) = '\0'; memmove(str, start, end - start + 2); } // Splits a line around the '=' into a key and value pair. static int KV_Parse_Line(char *line, char *key, char *value) { char *delimiter = strchr(line, '='); if (delimiter == NULL) return 0; *delimiter = '\0'; strncpy(key, line, MAX_KEY_LENGTH - 1); strncpy(value, delimiter + 1, MAX_VALUE_LENGTH - 1); key[MAX_KEY_LENGTH - 1] = '\0'; value[MAX_VALUE_LENGTH - 1] = '\0'; KV_Trim(key); KV_Trim(value); return 1; } SystemKResult_T KV_Get_Value_string(const char *filename, const char *search_key, char *value) { FILE *file = fopen(filename, "r"); if (file == NULL) { KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename); return SYSTEMK_RESULT_FILE_NOT_FOUND; } char line[MAX_LINE_LENGTH]; char key[MAX_KEY_LENGTH]; while (fgets(line, sizeof(line), file)) { if (KV_Parse_Line(line, key, value) && strcmp(key, search_key) == 0) { fclose(file); return SYSTEMK_RESULT_SUCCESS; } } fclose(file); KLOG_ERROR(TAG, "Couldn't find key %s in file %s!", search_key, filename); return SYSTEMK_RESULT_KEY_NOT_FOUND; } SystemKResult_T KV_Set_Value_string(const char *filename, const char *set_key, const char *set_value) { FILE *file = fopen(filename, "r"); if (file == NULL) { KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename); return SYSTEMK_RESULT_FILE_NOT_FOUND; } FILE *temp = fopen(TEMP_FILE, "w"); if (temp == NULL) { fclose(file); KLOG_ERROR(TAG, "Couldn't open file %s for writing!", TEMP_FILE); return SYSTEMK_RESULT_WRITE_FAILED; } char line[MAX_LINE_LENGTH]; char line_copy[MAX_LINE_LENGTH]; char key[MAX_KEY_LENGTH]; char value[MAX_VALUE_LENGTH]; int found = 0; while (fgets(line, sizeof(line), file)) { strncpy(line_copy, line, MAX_LINE_LENGTH); line_copy[MAX_LINE_LENGTH - 1] = '\0'; // Ensure null-termination if (KV_Parse_Line(line, key, value) && strcmp(key, set_key) == 0) { fprintf(temp, "%s = %s\n", set_key, set_value); found = 1; } else { fputs(line_copy, temp); } } if (!found) { fprintf(temp, "%s = %s\n", set_key, set_value); } fclose(file); fclose(temp); remove(filename); rename(TEMP_FILE, filename); return SYSTEMK_RESULT_SUCCESS; } SystemKResult_T KV_Get_Value_uint32(const char *filename, const char *search_key, uint32_t *value) { char value_str[MAX_VALUE_LENGTH]; SystemKResult_T result = KV_Get_Value_string(filename, search_key, value_str); if (result != SYSTEMK_RESULT_SUCCESS) { return result; } char *endptr; // Reset errno to check for conversion errors. errno = 0; *value = strtoul(value_str, &endptr, 10); // Check for conversion errors if ((errno == ERANGE && *value == ULONG_MAX) || (errno != 0)) { KLOG_ERROR(TAG, "Error converting %s for key %s to uint32_t!", value_str, search_key); return SYSTEMK_RESULT_WRONG_DATATYPE; } if (endptr == value_str) { KLOG_ERROR(TAG, "No digits were found in %s (for key %s)!", value_str, search_key); return SYSTEMK_RESULT_WRONG_DATATYPE; } if (*value > UINT32_MAX) { KLOG_ERROR(TAG, "Value %s (for key %s) exceeds uint32_t range!", value_str, search_key); return SYSTEMK_RESULT_OVERFLOW; } return SYSTEMK_RESULT_SUCCESS; } SystemKResult_T KV_Set_Value_uint32(const char *filename, const char *set_key, uint32_t *set_value) { char value_str[MAX_VALUE_LENGTH]; int written = snprintf(value_str, MAX_VALUE_LENGTH, "%lu", *set_value); if (written < 0 || written >= MAX_VALUE_LENGTH) { KLOG_ERROR(TAG, "Error converting uint32_t to string for key %s!", set_key); return SYSTEMK_RESULT_WRONG_DATATYPE; } return KV_Set_Value_string(filename, set_key, value_str); } SystemKResult_T KV_Get_Value_uint8(const char *filename, const char *search_key, uint8_t *value) { uint32_t value32 = 0; SystemKResult_T result = KV_Get_Value_uint32(filename, search_key, &value32); if (result != SYSTEMK_RESULT_SUCCESS) { return result; } // Check for conversion errors if (value32 > UINT8_MAX) { KLOG_ERROR(TAG, "Value %lu (for key %s) exceeds uint32_t range!", value32, search_key); return SYSTEMK_RESULT_OVERFLOW; } *value = (uint8_t)value32; return SYSTEMK_RESULT_SUCCESS; } SystemKResult_T KV_Set_Value_uint8(const char *filename, const char *set_key, uint8_t *set_value) { char value_str[MAX_VALUE_LENGTH]; int written = snprintf(value_str, MAX_VALUE_LENGTH, "%u", *set_value); if (written < 0 || written >= MAX_VALUE_LENGTH) { KLOG_ERROR(TAG, "Error converting uint8_t to string for key %s!", set_key); return SYSTEMK_RESULT_WRONG_DATATYPE; } return KV_Set_Value_string(filename, set_key, value_str); }