/* * Copyright (c) 2025 Amlogic, Inc. All rights reserved. * * This source code is subject to the terms and conditions defined in the * file 'LICENSE' which is part of this source code package. * * */ #include "tinyparam.h" #include #include #include #include #include #include #include "aml_log.h" /** * @brief Open and parse a JSON file * * @param file Path to the JSON file * @return tp_handle_t* Handle for the opened file, or NULL on failure */ tp_handle_t *tp_open(char *file) { tp_handle_t *handle = NULL; cJSON *root = NULL; char *buf = NULL; FILE *fp = NULL; // Check if file exists if (access(file, F_OK) != 0) { AML_LOGE("File %s does not exist\n", file); return NULL; } // Allocate handle handle = (tp_handle_t *)calloc(1, sizeof(tp_handle_t)); if (!handle) { AML_LOGE("Memory allocation failed for handle\n"); return NULL; } // Initialize mutex if (pthread_mutex_init(&handle->lock, NULL) != 0) { AML_LOGE("Mutex initialization failed\n"); free(handle); return NULL; } // Save filename handle->filename = strdup(file); if (!handle->filename) { AML_LOGE("Memory allocation failed for filename\n"); pthread_mutex_destroy(&handle->lock); free(handle); return NULL; } // Open file in read-write mode fp = fopen(file, "r+b"); if (!fp) { AML_LOGE("Failed to open file %s: %s\n", file, strerror(errno)); pthread_mutex_destroy(&handle->lock); free(handle->filename); free(handle); return NULL; } handle->fp = fp; // Reset file pointer to start if (fseek(handle->fp, 0, SEEK_SET) != 0) { AML_LOGE("Failed to seek to start of file %s: %s\n", file, strerror(errno)); goto fail; } // Get file size struct stat st; if (stat(file, &st) != 0) { AML_LOGE("Failed to get file size for %s: %s\n", file, strerror(errno)); goto fail; } off_t size = st.st_size; // Allocate buffer for file content buf = (char *)calloc(size + 1, 1); // Use calloc to zero-initialize if (!buf) { AML_LOGE("Memory allocation failed for buffer\n"); goto fail; } // Read file content size_t total_read = 0; while (total_read < size) { size_t n = fread(buf + total_read, 1, size - total_read, handle->fp); if (n == 0) { if (feof(handle->fp)) break; AML_LOGE("Error reading file %s: %s\n", file, strerror(errno)); goto fail; } total_read += n; } if (total_read != size) { AML_LOGE("Failed to read file %s, read %zu bytes, expected %zu\n", file, total_read, size); goto fail; } buf[size] = '\0'; // Ensure null-terminated string // Parse JSON content handle->root = cJSON_Parse(buf); if (!handle->root) { AML_LOGE("Failed to parse JSON content: %s\n", cJSON_GetErrorPtr()); goto fail; } free(buf); return handle; fail: if (buf) free(buf); if (handle) { if (handle->fp) fclose(handle->fp); if (handle->filename) free(handle->filename); pthread_mutex_destroy(&handle->lock); free(handle); } return NULL; } /** * @brief Close the handle and release resources * * @param h Handle to the JSON file */ void tp_close(tp_handle_t *h) { if (h) { if (h->fp) { fflush(h->fp); fsync(fileno(h->fp)); fclose(h->fp); } if (h->root) { cJSON_Delete(h->root); } if (h->filename) { free(h->filename); } pthread_mutex_destroy(&h->lock); free(h); } } /** * @brief Get a value from the JSON tree using a dotted key or single-level key * * @param h Handle to the JSON file * @param key Key in format "system.audio.volume" or single-level "volume" * @return char* Value corresponding to the key, or NULL if not found */ char* tp_get(tp_handle_t *h, char *key) { if (!h || !key) { AML_LOGE("Invalid handle or key\n"); return NULL; } if (!h->root) { AML_LOGE("JSON root is empty\n"); return NULL; } // Duplicate key to avoid modifying the original char *str = strdup(key); if (!str) { AML_LOGE("Memory allocation failed for key\n"); return NULL; } cJSON *cur = h->root; char *ptr = NULL; char *p = NULL; pthread_mutex_lock(&h->lock); // Check if key is single-level (no dots) if (strchr(str, '.') == NULL) { cur = cJSON_GetObjectItem(h->root, str); if (!cur || !cur->valuestring) { AML_LOGE("Single-level key not found or invalid: %s\n", str); free(str); pthread_mutex_unlock(&h->lock); return NULL; } char *result = strdup(cur->valuestring); free(str); pthread_mutex_unlock(&h->lock); if (!result) { AML_LOGE("Memory allocation failed for result\n"); return NULL; } return result; } // Handle multi-level key ptr = strtok_r(str, ".", &p); if (!ptr) { AML_LOGE("Invalid key format: %s\n", key); free(str); pthread_mutex_unlock(&h->lock); return NULL; } cur = cJSON_GetObjectItem(h->root, ptr); while (cur && cur->type == cJSON_Object) { ptr = strtok_r(NULL, ".", &p); if (!ptr) { AML_LOGE("Incomplete key path: %s\n", key); free(str); pthread_mutex_unlock(&h->lock); return NULL; } cur = cJSON_GetObjectItem(cur, ptr); } free(str); if (!cur || !cur->valuestring) { AML_LOGE("Key not found or invalid: %s\n", key); pthread_mutex_unlock(&h->lock); return NULL; } char *result = strdup(cur->valuestring); pthread_mutex_unlock(&h->lock); if (!result) { AML_LOGE("Memory allocation failed for result\n"); return NULL; } return result; } /** * @brief Set a value in the JSON tree using a dotted key or single-level key and write to file * * @param h Handle to the JSON file * @param key Key in format "system.audio.volume" or single-level "volume" * @param value Value to set * @return int 0 on success, -1 on failure */ int tp_set(tp_handle_t *h, char *key, char *value) { if (!h || !key || !value || !h->filename) { AML_LOGE("Invalid handle, key, value, or filename\n"); return -1; } if (!h->root) { AML_LOGE("JSON root is empty\n"); return -1; } // Duplicate key to avoid modifying the original char *str = strdup(key); if (!str) { AML_LOGE("Memory allocation failed for key\n"); return -1; } cJSON *cur = h->root; char *ptr = NULL; char *p = NULL; pthread_mutex_lock(&h->lock); // Handle single-level key if (strchr(str, '.') == NULL) { cur = cJSON_GetObjectItem(h->root, str); if (!cur) { AML_LOGE("Single-level key not found: %s\n", str); free(str); pthread_mutex_unlock(&h->lock); return -1; } // Update JSON node free(cur->valuestring); cur->valuestring = strdup(value); if (!cur->valuestring) { AML_LOGE("Memory allocation failed for value\n"); free(str); pthread_mutex_unlock(&h->lock); return -1; } } else { // Handle multi-level key ptr = strtok_r(str, ".", &p); if (!ptr) { AML_LOGE("Invalid key format: %s\n", key); free(str); pthread_mutex_unlock(&h->lock); return -1; } cur = cJSON_GetObjectItem(h->root, ptr); while (cur && cur->type == cJSON_Object) { ptr = strtok_r(NULL, ".", &p); if (!ptr) { AML_LOGE("Incomplete key path: %s\n", key); free(str); pthread_mutex_unlock(&h->lock); return -1; } cur = cJSON_GetObjectItem(cur, ptr); } if (!cur) { AML_LOGE("Key not found: %s\n", key); free(str); pthread_mutex_unlock(&h->lock); return -1; } // Update JSON node free(cur->valuestring); cur->valuestring = strdup(value); if (!cur->valuestring) { AML_LOGE("Memory allocation failed for value\n"); free(str); pthread_mutex_unlock(&h->lock); return -1; } } // Write to temporary file char temp_file[256]; snprintf(temp_file, sizeof(temp_file), "%s.tmp", h->filename); FILE *temp_fp = fopen(temp_file, "w"); if (!temp_fp) { AML_LOGE("Failed to open temporary file %s: %s\n", temp_file, strerror(errno)); free(cur->valuestring); cur->valuestring = NULL; free(str); pthread_mutex_unlock(&h->lock); return -1; } // Print JSON to buffer and write to temporary file char *buf = cJSON_Print(h->root); if (!buf) { AML_LOGE("Failed to serialize JSON\n"); fclose(temp_fp); free(cur->valuestring); cur->valuestring = NULL; free(str); pthread_mutex_unlock(&h->lock); return -1; } size_t len = fwrite(buf, 1, strlen(buf), temp_fp); if (len != strlen(buf)) { AML_LOGE("Failed to write to temporary file %s, wrote %zu bytes, expected %zu: %s\n", temp_file, len, strlen(buf), strerror(errno)); fclose(temp_fp); free(buf); free(cur->valuestring); cur->valuestring = NULL; free(str); pthread_mutex_unlock(&h->lock); return -1; } fclose(temp_fp); free(buf); // Replace original file with temporary file if (rename(temp_file, h->filename) != 0) { AML_LOGE("Failed to rename %s to %s: %s\n", temp_file, h->filename, strerror(errno)); free(cur->valuestring); cur->valuestring = NULL; free(str); pthread_mutex_unlock(&h->lock); return -1; } // Reopen original file to keep handle->fp valid fclose(h->fp); h->fp = fopen(h->filename, "r+"); if (!h->fp) { AML_LOGE("Failed to reopen original file %s: %s\n", h->filename, strerror(errno)); free(cur->valuestring); cur->valuestring = NULL; free(str); pthread_mutex_unlock(&h->lock); return -1; } free(str); pthread_mutex_unlock(&h->lock); return 0; }