/**************************************************************************** * apps/system/nxpkg/pkg_metadata.c * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include "netutils/cJSON.h" #include "pkg.h" /**************************************************************************** * Private Functions ****************************************************************************/ static int pkg_copy_string(FAR char *dest, size_t size, FAR const char *src) { int ret; ret = snprintf(dest, size, "%s", src); if (ret < 0) { return ret; } if ((size_t)ret >= size) { return -ENAMETOOLONG; } return 0; } static bool pkg_string_equal(FAR const char *lhs, size_t lhs_size, FAR const char *rhs, size_t rhs_size) { size_t lhs_len; size_t rhs_len; lhs_len = strnlen(lhs, lhs_size); rhs_len = strnlen(rhs, rhs_size); if (lhs_len >= lhs_size || rhs_len >= rhs_size) { return false; } return lhs_len == rhs_len && strncmp(lhs, rhs, lhs_len) == 0; } static int pkg_string_cmp(FAR const char *lhs, size_t lhs_size, FAR const char *rhs, size_t rhs_size) { size_t lhs_len; size_t rhs_len; size_t cmp_len; lhs_len = strnlen(lhs, lhs_size); rhs_len = strnlen(rhs, rhs_size); cmp_len = lhs_len > rhs_len ? lhs_len : rhs_len; if (lhs_len >= lhs_size || rhs_len >= rhs_size) { return strncmp(lhs, rhs, cmp_len); } return strncmp(lhs, rhs, cmp_len + 1); } static FAR cJSON *pkg_metadata_packages_array(FAR cJSON *root) { if (cJSON_IsArray(root)) { return root; } return cJSON_GetObjectItemCaseSensitive(root, "packages"); } static int pkg_metadata_parse_manifest(FAR cJSON *item, FAR struct pkg_manifest_s *manifest) { FAR cJSON *field; FAR const char *value; int ret; memset(manifest, 0, sizeof(*manifest)); field = cJSON_GetObjectItemCaseSensitive(item, "name"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(manifest->name, sizeof(manifest->name), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "version"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(manifest->version, sizeof(manifest->version), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "arch"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(manifest->arch, sizeof(manifest->arch), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "compat"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(manifest->compat, sizeof(manifest->compat), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "artifact"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(manifest->artifact, sizeof(manifest->artifact), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "sha256"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(manifest->sha256, sizeof(manifest->sha256), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "type"); value = cJSON_GetStringValue(field); if (pkg_manifest_parse_type(value, &manifest->type) < 0) { return -EINVAL; } return pkg_manifest_validate(manifest); } static int pkg_metadata_parse_versions(FAR cJSON *versions, FAR struct pkg_installed_entry_s *entry) { FAR cJSON *item; size_t count = 0; if (!cJSON_IsArray(versions)) { return -EINVAL; } cJSON_ArrayForEach(item, versions) { FAR const char *value; int ret; if (count >= PKG_INSTALLED_VERSIONS_MAX) { return -E2BIG; } value = cJSON_GetStringValue(item); if (value == NULL) { return -EINVAL; } ret = pkg_copy_string(entry->versions[count], sizeof(entry->versions[count]), value); if (ret < 0) { return ret; } count++; } entry->version_count = count; return 0; } static int pkg_metadata_parse_installed_entry( FAR cJSON *item, FAR struct pkg_installed_entry_s *entry) { FAR cJSON *field; FAR const char *value; int ret; memset(entry, 0, sizeof(*entry)); field = cJSON_GetObjectItemCaseSensitive(item, "name"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(entry->name, sizeof(entry->name), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "current"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(entry->current, sizeof(entry->current), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "previous"); value = cJSON_GetStringValue(field); if (value != NULL) { ret = pkg_copy_string(entry->previous, sizeof(entry->previous), value); if (ret < 0) { return ret; } } field = cJSON_GetObjectItemCaseSensitive(item, "arch"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(entry->arch, sizeof(entry->arch), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "compat"); value = cJSON_GetStringValue(field); if (value == NULL || (ret = pkg_copy_string(entry->compat, sizeof(entry->compat), value)) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "type"); value = cJSON_GetStringValue(field); if (pkg_manifest_parse_type(value, &entry->type) < 0) { return -EINVAL; } field = cJSON_GetObjectItemCaseSensitive(item, "versions"); ret = pkg_metadata_parse_versions(field, entry); if (ret < 0) { return ret; } return 0; } static int pkg_metadata_version_token_cmp(FAR const char *lhs, FAR const char *rhs) { long leftnum; long rightnum; FAR char *leftend; FAR char *rightend; leftnum = strtol(lhs, &leftend, 10); rightnum = strtol(rhs, &rightend, 10); if (leftend != lhs && rightend != rhs) { if (leftnum < rightnum) { return -1; } if (leftnum > rightnum) { return 1; } } else { int ret; ret = pkg_string_cmp(lhs, PKG_VERSION_MAX + 1, rhs, PKG_VERSION_MAX + 1); if (ret < 0) { return -1; } if (ret > 0) { return 1; } } return 0; } static int pkg_metadata_version_cmp(FAR const char *lhs, FAR const char *rhs) { while (*lhs != '\0' || *rhs != '\0') { char leftbuf[16]; char rightbuf[16]; size_t leftlen = 0; size_t rightlen = 0; int ret; while (lhs[leftlen] != '\0' && lhs[leftlen] != '.') { if (leftlen + 1 >= sizeof(leftbuf)) { return pkg_string_cmp(lhs, PKG_VERSION_MAX + 1, rhs, PKG_VERSION_MAX + 1); } leftbuf[leftlen] = lhs[leftlen]; leftlen++; } while (rhs[rightlen] != '\0' && rhs[rightlen] != '.') { if (rightlen + 1 >= sizeof(rightbuf)) { return pkg_string_cmp(lhs, PKG_VERSION_MAX + 1, rhs, PKG_VERSION_MAX + 1); } rightbuf[rightlen] = rhs[rightlen]; rightlen++; } leftbuf[leftlen] = '\0'; rightbuf[rightlen] = '\0'; ret = pkg_metadata_version_token_cmp(leftbuf, rightbuf); if (ret != 0) { return ret; } lhs += leftlen; rhs += rightlen; if (*lhs == '.') { lhs++; } if (*rhs == '.') { rhs++; } } return 0; } static FAR cJSON *pkg_metadata_manifest_to_json( FAR const struct pkg_manifest_s *manifest) { FAR cJSON *root; root = cJSON_CreateObject(); if (root == NULL) { return NULL; } cJSON_AddStringToObject(root, "name", manifest->name); cJSON_AddStringToObject(root, "version", manifest->version); cJSON_AddStringToObject(root, "arch", manifest->arch); cJSON_AddStringToObject(root, "compat", manifest->compat); cJSON_AddStringToObject(root, "artifact", manifest->artifact); cJSON_AddStringToObject(root, "sha256", manifest->sha256); cJSON_AddStringToObject(root, "type", pkg_manifest_type_str(manifest->type)); return root; } /**************************************************************************** * Public Functions ****************************************************************************/ int pkg_metadata_load_index(FAR struct pkg_index_s *index) { FAR cJSON *root; FAR cJSON *packages; FAR cJSON *item; FAR char *text; char path[PATH_MAX]; size_t count = 0; size_t textlen; int ret; if (index == NULL) { return -EINVAL; } memset(index, 0, sizeof(*index)); ret = pkg_store_format_index_path(path, sizeof(path)); if (ret < 0) { return ret; } pkg_info("loading index from %s", path); ret = pkg_store_read_text(path, &text); if (ret < 0) { return ret; } textlen = strlen(text); pkg_info("index read complete (%zu bytes)", textlen); root = cJSON_Parse(text); pkg_info("cJSON_Parse returned %s", root != NULL ? "success" : "failure"); free(text); if (root == NULL) { return -EINVAL; } packages = pkg_metadata_packages_array(root); if (!cJSON_IsArray(packages)) { cJSON_Delete(root); return -EINVAL; } cJSON_ArrayForEach(item, packages) { if (count >= PKG_INDEX_MAX) { cJSON_Delete(root); return -E2BIG; } ret = pkg_metadata_parse_manifest(item, &index->manifests[count]); if (ret < 0) { cJSON_Delete(root); return ret; } pkg_info("parsed manifest %s %s", index->manifests[count].name, index->manifests[count].version); count++; } index->count = count; cJSON_Delete(root); return 0; } FAR const struct pkg_manifest_s * pkg_metadata_find_latest(FAR const struct pkg_index_s *index, FAR const char *name) { FAR const struct pkg_manifest_s *best = NULL; FAR const char *arch; FAR const char *compat; size_t i; if (index == NULL || name == NULL) { return NULL; } arch = pkg_runtime_arch(); compat = pkg_runtime_compat(); for (i = 0; i < index->count; i++) { FAR const struct pkg_manifest_s *candidate = &index->manifests[i]; if (!pkg_string_equal(candidate->name, sizeof(candidate->name), name, PKG_NAME_MAX + 1)) { continue; } if (!pkg_string_equal(candidate->arch, sizeof(candidate->arch), arch, PKG_ARCH_MAX + 1) || !pkg_string_equal(candidate->compat, sizeof(candidate->compat), compat, PKG_COMPAT_MAX + 1)) { continue; } if (best == NULL || pkg_metadata_version_cmp(candidate->version, best->version) > 0) { best = candidate; } } return best; } int pkg_metadata_load_installed(FAR struct pkg_installed_db_s *db) { FAR cJSON *root; FAR cJSON *packages; FAR cJSON *item; FAR char *text; char path[PATH_MAX]; size_t count = 0; int ret; if (db == NULL) { return -EINVAL; } memset(db, 0, sizeof(*db)); ret = pkg_store_format_installed_path(path, sizeof(path)); if (ret < 0) { return ret; } ret = pkg_store_read_text(path, &text); if (ret == -ENOENT) { return 0; } if (ret < 0) { return ret; } root = cJSON_Parse(text); free(text); if (root == NULL) { return -EINVAL; } packages = pkg_metadata_packages_array(root); if (!cJSON_IsArray(packages)) { cJSON_Delete(root); return -EINVAL; } cJSON_ArrayForEach(item, packages) { if (count >= PKG_INSTALLED_MAX) { cJSON_Delete(root); return -E2BIG; } ret = pkg_metadata_parse_installed_entry(item, &db->entries[count]); if (ret < 0) { cJSON_Delete(root); return ret; } count++; } db->count = count; cJSON_Delete(root); return 0; } int pkg_metadata_save_installed(FAR const struct pkg_installed_db_s *db) { FAR cJSON *root; FAR cJSON *packages; FAR char *text; char path[PATH_MAX]; size_t i; int ret; if (db == NULL) { return -EINVAL; } root = cJSON_CreateObject(); if (root == NULL) { return -ENOMEM; } packages = cJSON_AddArrayToObject(root, "packages"); if (packages == NULL) { cJSON_Delete(root); return -ENOMEM; } for (i = 0; i < db->count; i++) { FAR const struct pkg_installed_entry_s *entry = &db->entries[i]; FAR cJSON *item = cJSON_CreateObject(); FAR cJSON *versions = cJSON_AddArrayToObject(item, "versions"); size_t j; if (item == NULL || versions == NULL) { cJSON_Delete(root); return -ENOMEM; } cJSON_AddStringToObject(item, "name", entry->name); cJSON_AddStringToObject(item, "current", entry->current); cJSON_AddStringToObject(item, "previous", entry->previous); cJSON_AddStringToObject(item, "arch", entry->arch); cJSON_AddStringToObject(item, "compat", entry->compat); cJSON_AddStringToObject(item, "type", pkg_manifest_type_str(entry->type)); for (j = 0; j < entry->version_count; j++) { cJSON_AddItemToArray(versions, cJSON_CreateString(entry->versions[j])); } cJSON_AddItemToArray(packages, item); } text = cJSON_PrintUnformatted(root); cJSON_Delete(root); if (text == NULL) { return -ENOMEM; } ret = pkg_store_format_installed_path(path, sizeof(path)); if (ret < 0) { cJSON_free(text); return ret; } ret = pkg_store_write_text_atomic(path, text); cJSON_free(text); return ret; } FAR struct pkg_installed_entry_s * pkg_metadata_find_installed(FAR struct pkg_installed_db_s *db, FAR const char *name) { size_t i; if (db == NULL || name == NULL) { return NULL; } for (i = 0; i < db->count; i++) { if (pkg_string_equal(db->entries[i].name, sizeof(db->entries[i].name), name, PKG_NAME_MAX + 1)) { return &db->entries[i]; } } return NULL; } int pkg_metadata_write_manifest(FAR const char *path, FAR const struct pkg_manifest_s *manifest) { FAR cJSON *root; FAR char *text; int ret; root = pkg_metadata_manifest_to_json(manifest); if (root == NULL) { return -ENOMEM; } text = cJSON_PrintUnformatted(root); cJSON_Delete(root); if (text == NULL) { return -ENOMEM; } ret = pkg_store_write_text_atomic(path, text); cJSON_free(text); return ret; } int pkg_metadata_print_installed(FAR FILE *stream, FAR const struct pkg_installed_db_s *db) { size_t i; if (stream == NULL || db == NULL) { return -EINVAL; } for (i = 0; i < db->count; i++) { FAR const struct pkg_installed_entry_s *entry = &db->entries[i]; size_t j; fprintf( stream, "%s current=%s previous=%s type=%s arch=%s compat=%s versions=", entry->name, entry->current[0] != '\0' ? entry->current : "-", entry->previous[0] != '\0' ? entry->previous : "-", pkg_manifest_type_str(entry->type), entry->arch, entry->compat); for (j = 0; j < entry->version_count; j++) { fprintf(stream, "%s%s", j == 0 ? "" : ",", entry->versions[j]); } fputc('\n', stream); } return 0; }