400 lines
10 KiB
C
400 lines
10 KiB
C
/*
|
|
* 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 <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#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;
|
|
}
|