system/nxpkg: add local package lifecycle helper

Add the initial nxpkg command, metadata and store handling, the local install/list path, and the repository export helper.

Keep the current flow scoped to local artifacts and target-qualified repository entries so it remains usable as an incremental MVP while follow-up features land separately.

Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
This commit is contained in:
aviralgarg05 2026-05-18 15:56:33 +05:30 committed by Alan C. Assis
parent 477d04b975
commit 65854ea805
15 changed files with 2920 additions and 0 deletions

View file

@ -0,0 +1,43 @@
# ##############################################################################
# apps/system/nxpkg/CMakeLists.txt
#
# 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.
#
# ##############################################################################
if(CONFIG_SYSTEM_NXPKG)
nuttx_add_application(
MODULE
${CONFIG_SYSTEM_NXPKG}
NAME
${CONFIG_SYSTEM_NXPKG_PROGNAME}
STACKSIZE
${CONFIG_SYSTEM_NXPKG_STACKSIZE}
PRIORITY
${CONFIG_SYSTEM_NXPKG_PRIORITY}
SRCS
pkg_main.c
pkg_compat.c
pkg_hash.c
pkg_install.c
pkg_log.c
pkg_manifest.c
pkg_metadata.c
pkg_store.c
pkg_txn.c)
endif()

32
system/nxpkg/Kconfig Normal file
View file

@ -0,0 +1,32 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
config SYSTEM_NXPKG
bool "'nxpkg' lifecycle helper"
default n
select CRYPTO
select NETUTILS_CJSON
---help---
Enable the thin package lifecycle helper proposed for the
Dynamic ELF distribution work.
if SYSTEM_NXPKG
config SYSTEM_NXPKG_PROGNAME
string "Program name"
default "nxpkg"
---help---
This is the name of the program that will be used when the
nxpkg tool is installed.
config SYSTEM_NXPKG_PRIORITY
int "'nxpkg' task priority"
default 100
config SYSTEM_NXPKG_STACKSIZE
int "'nxpkg' stack size"
default 16384
endif

25
system/nxpkg/Make.defs Normal file
View file

@ -0,0 +1,25 @@
############################################################################
# apps/system/nxpkg/Make.defs
#
# 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.
#
############################################################################
ifneq ($(CONFIG_SYSTEM_NXPKG),)
CONFIGURED_APPS += $(APPDIR)/system/nxpkg
endif

34
system/nxpkg/Makefile Normal file
View file

@ -0,0 +1,34 @@
############################################################################
# apps/system/nxpkg/Makefile
#
# 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.
#
############################################################################
include $(APPDIR)/Make.defs
PROGNAME = $(CONFIG_SYSTEM_NXPKG_PROGNAME)
PRIORITY = $(CONFIG_SYSTEM_NXPKG_PRIORITY)
STACKSIZE = $(CONFIG_SYSTEM_NXPKG_STACKSIZE)
MODULE = $(CONFIG_SYSTEM_NXPKG)
CSRCS = pkg_compat.c pkg_hash.c pkg_install.c pkg_log.c pkg_manifest.c
CSRCS += pkg_metadata.c pkg_store.c pkg_txn.c
MAINSRC = pkg_main.c
include $(APPDIR)/Application.mk

187
system/nxpkg/pkg.h Normal file
View file

@ -0,0 +1,187 @@
/****************************************************************************
* apps/system/nxpkg/pkg.h
*
* 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.
*
****************************************************************************/
#ifndef __APPS_SYSTEM_NXPKG_PKG_H
#define __APPS_SYSTEM_NXPKG_PKG_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define PKG_REPO_DIR "/etc/nxpkg"
#define PKG_REPO_INDEX "/etc/nxpkg/index.json"
#define PKG_REPO_INSTALLED "/var/lib/nxpkg/installed.json"
#define PKG_STORE_DIR "/var/lib/nxpkg/pkgs"
#define PKG_TMP_DIR "/var/cache/nxpkg"
#define PKG_TMP_PKG_DIR "/var/cache/nxpkg/pkg"
#define PKG_NAME_MAX 63
#define PKG_VERSION_MAX 31
#define PKG_ARCH_MAX 31
#define PKG_COMPAT_MAX 63
#define PKG_HASH_HEX_LEN 64
#define PKG_INDEX_MAX 32
#define PKG_INSTALLED_MAX 16
#define PKG_INSTALLED_VERSIONS_MAX 8
/****************************************************************************
* Public Types
****************************************************************************/
enum pkg_payload_type_e
{
PKG_PAYLOAD_ELF = 0,
PKG_PAYLOAD_SHARED_LIB
};
enum pkg_txn_state_e
{
PKG_TXN_IDLE = 0,
PKG_TXN_FETCHING,
PKG_TXN_VERIFIED,
PKG_TXN_STAGED,
PKG_TXN_COMPAT_OK,
PKG_TXN_ACTIVATED,
PKG_TXN_CLEANUP,
PKG_TXN_FAILED,
PKG_TXN_RESTORE
};
struct pkg_manifest_s
{
char name[PKG_NAME_MAX + 1];
char version[PKG_VERSION_MAX + 1];
char arch[PKG_ARCH_MAX + 1];
char compat[PKG_COMPAT_MAX + 1];
char artifact[PATH_MAX];
char sha256[PKG_HASH_HEX_LEN + 1];
enum pkg_payload_type_e type;
};
struct pkg_index_s
{
struct pkg_manifest_s manifests[PKG_INDEX_MAX];
size_t count;
};
struct pkg_installed_entry_s
{
char name[PKG_NAME_MAX + 1];
char current[PKG_VERSION_MAX + 1];
char previous[PKG_VERSION_MAX + 1];
char arch[PKG_ARCH_MAX + 1];
char compat[PKG_COMPAT_MAX + 1];
char versions[PKG_INSTALLED_VERSIONS_MAX][PKG_VERSION_MAX + 1];
enum pkg_payload_type_e type;
size_t version_count;
};
struct pkg_installed_db_s
{
struct pkg_installed_entry_s entries[PKG_INSTALLED_MAX];
size_t count;
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
const char *pkg_manifest_type_str(enum pkg_payload_type_e type);
int pkg_manifest_validate(FAR const struct pkg_manifest_s *manifest);
int pkg_manifest_parse_type(FAR const char *value,
FAR enum pkg_payload_type_e *type);
int pkg_store_prepare_layout(void);
int pkg_store_ensure_package_root(FAR const char *name);
int pkg_store_ensure_version_dir(FAR const char *name,
FAR const char *version);
int pkg_store_format_index_path(FAR char *buffer, size_t size);
int pkg_store_format_installed_path(FAR char *buffer, size_t size);
int pkg_store_format_package_root(FAR char *buffer, size_t size,
FAR const char *name);
int pkg_store_format_version_path(FAR char *buffer, size_t size,
FAR const char *name,
FAR const char *version);
int pkg_store_format_current_path(FAR char *buffer, size_t size,
FAR const char *name);
int pkg_store_format_previous_path(FAR char *buffer, size_t size,
FAR const char *name);
int pkg_store_format_txn_path(FAR char *buffer, size_t size,
FAR const char *name);
int pkg_store_format_lock_path(FAR char *buffer, size_t size,
FAR const char *name);
int pkg_store_format_download_path(FAR char *buffer, size_t size,
FAR const char *name,
FAR const char *version);
int pkg_store_format_payload_path(FAR char *buffer, size_t size,
FAR const char *name,
FAR const char *version,
FAR const char *artifact);
int pkg_store_format_manifest_path(FAR char *buffer, size_t size,
FAR const char *name,
FAR const char *version);
int pkg_store_read_text(FAR const char *path, FAR char **buffer);
int pkg_store_write_text_atomic(FAR const char *path, FAR const char *text);
int pkg_store_copy_file(FAR const char *src, FAR const char *dest);
int pkg_store_remove_file(FAR const char *path);
const char *pkg_runtime_arch(void);
const char *pkg_runtime_compat(void);
int pkg_compat_check(FAR const struct pkg_manifest_s *manifest);
int pkg_hash_file_sha256(FAR const char *path,
FAR char digest[PKG_HASH_HEX_LEN + 1]);
int pkg_metadata_load_index(FAR struct pkg_index_s *index);
FAR const struct pkg_manifest_s *
pkg_metadata_find_latest(FAR const struct pkg_index_s *index,
FAR const char *name);
int pkg_metadata_load_installed(FAR struct pkg_installed_db_s *db);
int pkg_metadata_save_installed(FAR const struct pkg_installed_db_s *db);
FAR struct pkg_installed_entry_s *
pkg_metadata_find_installed(FAR struct pkg_installed_db_s *db,
FAR const char *name);
int pkg_metadata_write_manifest(FAR const char *path,
FAR const struct pkg_manifest_s *manifest);
int pkg_metadata_print_installed(FAR FILE *stream,
FAR const struct pkg_installed_db_s *db);
const char *pkg_txn_state_str(enum pkg_txn_state_e state);
int pkg_txn_write_state(FAR const char *name, enum pkg_txn_state_e state);
int pkg_txn_clear_state(FAR const char *name);
int pkg_install(FAR const char *name);
int pkg_list(FAR FILE *stream);
void pkg_error(FAR const char *fmt, ...);
void pkg_info(FAR const char *fmt, ...);
#endif /* __APPS_SYSTEM_NXPKG_PKG_H */

64
system/nxpkg/pkg_compat.c Normal file
View file

@ -0,0 +1,64 @@
/****************************************************************************
* apps/system/nxpkg/pkg_compat.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 <errno.h>
#include <string.h>
#include "pkg.h"
/****************************************************************************
* Public Functions
****************************************************************************/
const char *pkg_runtime_arch(void)
{
return CONFIG_ARCH;
}
const char *pkg_runtime_compat(void)
{
return CONFIG_ARCH_BOARD;
}
int pkg_compat_check(FAR const struct pkg_manifest_s *manifest)
{
if (manifest == NULL)
{
return -EINVAL;
}
if (strcmp(manifest->arch, pkg_runtime_arch()) != 0)
{
return -ENOEXEC;
}
if (strcmp(manifest->compat, pkg_runtime_compat()) != 0)
{
return -EXDEV;
}
return 0;
}

98
system/nxpkg/pkg_hash.c Normal file
View file

@ -0,0 +1,98 @@
/****************************************************************************
* apps/system/nxpkg/pkg_hash.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 <crypto/sha2.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include "pkg.h"
/****************************************************************************
* Private Functions
****************************************************************************/
static void pkg_hex_encode(FAR const uint8_t *input, size_t length,
FAR char *output)
{
static const char g_hex[] = "0123456789abcdef";
size_t i;
for (i = 0; i < length; i++)
{
output[i * 2] = g_hex[input[i] >> 4];
output[i * 2 + 1] = g_hex[input[i] & 0x0f];
}
output[length * 2] = '\0';
}
/****************************************************************************
* Public Functions
****************************************************************************/
int pkg_hash_file_sha256(FAR const char *path,
FAR char digest[PKG_HASH_HEX_LEN + 1])
{
SHA2_CTX ctx;
FAR FILE *stream;
uint8_t raw[SHA256_DIGEST_LENGTH];
uint8_t buffer[512];
size_t nread;
stream = fopen(path, "rb");
if (stream == NULL)
{
return -errno;
}
sha256init(&ctx);
for (; ; )
{
nread = fread(buffer, 1, sizeof(buffer), stream);
if (nread > 0)
{
sha256update(&ctx, buffer, nread);
}
if (nread < sizeof(buffer))
{
if (ferror(stream))
{
fclose(stream);
return -EIO;
}
break;
}
}
fclose(stream);
sha256final(raw, &ctx);
pkg_hex_encode(raw, sizeof(raw), digest);
return 0;
}

501
system/nxpkg/pkg_install.c Normal file
View file

@ -0,0 +1,501 @@
/****************************************************************************
* apps/system/nxpkg/pkg_install.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 <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include "pkg.h"
/****************************************************************************
* Private Functions
****************************************************************************/
static int pkg_install_resolve_artifact(FAR char *buffer, size_t size,
FAR const struct pkg_manifest_s
*manifest)
{
int ret;
if (manifest->artifact[0] == '/')
{
ret = snprintf(buffer, size, "%s", manifest->artifact);
if (ret < 0)
{
return ret;
}
return (size_t)ret >= size ? -ENAMETOOLONG : 0;
}
ret = snprintf(buffer, size, "%s/%s", PKG_REPO_DIR, manifest->artifact);
if (ret < 0)
{
return ret;
}
return (size_t)ret >= size ? -ENAMETOOLONG : 0;
}
static int pkg_install_acquire_lock(FAR const char *name, FAR char *path,
size_t size)
{
int fd;
int ret;
ret = pkg_store_ensure_package_root(name);
if (ret < 0)
{
return ret;
}
ret = pkg_store_format_lock_path(path, size, name);
if (ret < 0)
{
return ret;
}
fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
if (fd < 0)
{
return errno == EEXIST ? -EBUSY : -errno;
}
close(fd);
return 0;
}
static bool pkg_install_has_version(
FAR const struct pkg_installed_entry_s *entry,
FAR const char *version)
{
size_t i;
for (i = 0; i < entry->version_count; i++)
{
if (strcmp(entry->versions[i], version) == 0)
{
return true;
}
}
return false;
}
static int pkg_install_add_version(FAR struct pkg_installed_entry_s *entry,
FAR const char *version)
{
int ret;
if (pkg_install_has_version(entry, version))
{
return 0;
}
if (entry->version_count >= PKG_INSTALLED_VERSIONS_MAX)
{
return -E2BIG;
}
ret = snprintf(entry->versions[entry->version_count],
sizeof(entry->versions[entry->version_count]),
"%s", version);
if (ret < 0)
{
return ret;
}
if ((size_t)ret >= sizeof(entry->versions[entry->version_count]))
{
return -ENAMETOOLONG;
}
entry->version_count++;
return 0;
}
static int pkg_install_update_installed(FAR struct pkg_installed_db_s *db,
FAR const struct pkg_manifest_s
*manifest)
{
FAR struct pkg_installed_entry_s *entry;
int ret;
entry = pkg_metadata_find_installed(db, manifest->name);
if (entry == NULL)
{
if (db->count >= PKG_INSTALLED_MAX)
{
return -E2BIG;
}
entry = &db->entries[db->count++];
memset(entry, 0, sizeof(*entry));
ret = snprintf(entry->name, sizeof(entry->name), "%s", manifest->name);
if (ret < 0 || (size_t)ret >= sizeof(entry->name))
{
return -ENAMETOOLONG;
}
}
if (entry->current[0] != '\0' &&
strcmp(entry->current, manifest->version) != 0)
{
ret = snprintf(entry->previous, sizeof(entry->previous), "%s",
entry->current);
if (ret < 0 || (size_t)ret >= sizeof(entry->previous))
{
return -ENAMETOOLONG;
}
}
ret = snprintf(entry->current, sizeof(entry->current), "%s",
manifest->version);
if (ret < 0 || (size_t)ret >= sizeof(entry->current))
{
return -ENAMETOOLONG;
}
ret = snprintf(entry->arch, sizeof(entry->arch), "%s", manifest->arch);
if (ret < 0 || (size_t)ret >= sizeof(entry->arch))
{
return -ENAMETOOLONG;
}
ret = snprintf(entry->compat, sizeof(entry->compat), "%s",
manifest->compat);
if (ret < 0 || (size_t)ret >= sizeof(entry->compat))
{
return -ENAMETOOLONG;
}
entry->type = manifest->type;
return pkg_install_add_version(entry, manifest->version);
}
static int pkg_install_write_pointers(
FAR const struct pkg_installed_db_s *db,
FAR const struct pkg_manifest_s *manifest)
{
FAR struct pkg_installed_entry_s *entry;
char current[PATH_MAX];
char previous[PATH_MAX];
int ret;
entry = pkg_metadata_find_installed((FAR struct pkg_installed_db_s *)db,
manifest->name);
if (entry == NULL)
{
return -ENOENT;
}
ret = pkg_store_format_current_path(current, sizeof(current),
manifest->name);
if (ret < 0)
{
return ret;
}
ret = pkg_store_format_previous_path(previous, sizeof(previous),
manifest->name);
if (ret < 0)
{
return ret;
}
ret = pkg_store_write_text_atomic(current, entry->current);
if (ret < 0)
{
return ret;
}
return pkg_store_write_text_atomic(previous, entry->previous);
}
/****************************************************************************
* Public Functions
****************************************************************************/
int pkg_install(FAR const char *name)
{
FAR struct pkg_index_s *index;
FAR struct pkg_installed_db_s *installed;
FAR const struct pkg_manifest_s *manifest;
char source[PATH_MAX];
char tmp[PATH_MAX] = "";
char payload[PATH_MAX];
char manifest_path[PATH_MAX];
char lock[PATH_MAX] = "";
char digest[PKG_HASH_HEX_LEN + 1];
int ret;
index = malloc(sizeof(*index));
installed = malloc(sizeof(*installed));
if (index == NULL || installed == NULL)
{
free(index);
free(installed);
pkg_error("unable to allocate package metadata buffers");
return EXIT_FAILURE;
}
ret = pkg_store_prepare_layout();
if (ret < 0)
{
free(index);
free(installed);
pkg_error("unable to prepare package layout: %d", ret);
return EXIT_FAILURE;
}
ret = pkg_metadata_load_index(index);
if (ret < 0)
{
free(index);
free(installed);
pkg_error("unable to load local index metadata: %d", ret);
return EXIT_FAILURE;
}
manifest = pkg_metadata_find_latest(index, name);
if (manifest == NULL)
{
free(index);
free(installed);
pkg_error("package '%s' not found in local index", name);
return EXIT_FAILURE;
}
ret = pkg_install_resolve_artifact(source, sizeof(source), manifest);
if (ret < 0)
{
pkg_error("artifact path for '%s' is too long", name);
return EXIT_FAILURE;
}
ret = pkg_install_acquire_lock(name, lock, sizeof(lock));
if (ret < 0)
{
pkg_error("unable to acquire package lock for '%s': %d", name, ret);
return EXIT_FAILURE;
}
ret = pkg_txn_write_state(name, PKG_TXN_FETCHING);
if (ret < 0)
{
goto errout;
}
ret = pkg_store_format_download_path(tmp, sizeof(tmp), manifest->name,
manifest->version);
if (ret < 0)
{
goto errout;
}
ret = pkg_store_copy_file(source, tmp);
if (ret < 0)
{
goto errout;
}
ret = pkg_hash_file_sha256(tmp, digest);
if (ret < 0)
{
goto errout;
}
if (strcasecmp(digest, manifest->sha256) != 0)
{
ret = -EILSEQ;
goto errout;
}
ret = pkg_txn_write_state(name, PKG_TXN_VERIFIED);
if (ret < 0)
{
goto errout;
}
ret = pkg_store_ensure_version_dir(manifest->name, manifest->version);
if (ret < 0)
{
goto errout;
}
ret = pkg_store_format_payload_path(payload, sizeof(payload),
manifest->name, manifest->version,
manifest->artifact);
if (ret < 0)
{
goto errout;
}
ret = pkg_store_copy_file(tmp, payload);
if (ret < 0)
{
goto errout;
}
ret = pkg_store_format_manifest_path(manifest_path, sizeof(manifest_path),
manifest->name, manifest->version);
if (ret < 0)
{
goto errout;
}
ret = pkg_metadata_write_manifest(manifest_path, manifest);
if (ret < 0)
{
goto errout;
}
ret = pkg_txn_write_state(name, PKG_TXN_STAGED);
if (ret < 0)
{
goto errout;
}
ret = pkg_compat_check(manifest);
if (ret < 0)
{
goto errout;
}
ret = pkg_txn_write_state(name, PKG_TXN_COMPAT_OK);
if (ret < 0)
{
goto errout;
}
ret = pkg_metadata_load_installed(installed);
if (ret < 0)
{
goto errout;
}
ret = pkg_install_update_installed(installed, manifest);
if (ret < 0)
{
goto errout;
}
ret = pkg_install_write_pointers(installed, manifest);
if (ret < 0)
{
goto errout;
}
ret = pkg_metadata_save_installed(installed);
if (ret < 0)
{
goto errout;
}
ret = pkg_txn_write_state(name, PKG_TXN_ACTIVATED);
if (ret < 0)
{
goto errout;
}
pkg_txn_write_state(name, PKG_TXN_CLEANUP);
if (tmp[0] != '\0')
{
pkg_store_remove_file(tmp);
}
pkg_txn_clear_state(name);
if (lock[0] != '\0')
{
pkg_store_remove_file(lock);
}
pkg_info("installed %s version %s", manifest->name, manifest->version);
free(index);
free(installed);
return EXIT_SUCCESS;
errout:
pkg_txn_write_state(name, PKG_TXN_FAILED);
if (tmp[0] != '\0')
{
pkg_store_remove_file(tmp);
}
pkg_txn_clear_state(name);
if (lock[0] != '\0')
{
pkg_store_remove_file(lock);
}
free(index);
free(installed);
pkg_error("install failed for '%s': %d", name, ret);
return EXIT_FAILURE;
}
int pkg_list(FAR FILE *stream)
{
FAR struct pkg_installed_db_s *db;
int ret;
db = malloc(sizeof(*db));
if (db == NULL)
{
pkg_error("unable to allocate installed metadata buffer");
return EXIT_FAILURE;
}
ret = pkg_store_prepare_layout();
if (ret < 0)
{
free(db);
pkg_error("unable to prepare package layout: %d", ret);
return EXIT_FAILURE;
}
ret = pkg_metadata_load_installed(db);
if (ret < 0)
{
free(db);
pkg_error("unable to load installed metadata: %d", ret);
return EXIT_FAILURE;
}
ret = pkg_metadata_print_installed(stream, db);
if (ret < 0)
{
free(db);
pkg_error("unable to print installed metadata: %d", ret);
return EXIT_FAILURE;
}
free(db);
return EXIT_SUCCESS;
}

65
system/nxpkg/pkg_log.c Normal file
View file

@ -0,0 +1,65 @@
/****************************************************************************
* apps/system/nxpkg/pkg_log.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 <stdarg.h>
#include <stdio.h>
#include "pkg.h"
/****************************************************************************
* Private Functions
****************************************************************************/
static void pkg_vlog(FAR FILE *stream, FAR const char *level,
FAR const char *fmt, va_list ap)
{
fprintf(stream, "nxpkg: %s: ", level);
vfprintf(stream, fmt, ap);
fputc('\n', stream);
fflush(stream);
}
/****************************************************************************
* Public Functions
****************************************************************************/
void pkg_error(FAR const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
pkg_vlog(stderr, "error", fmt, ap);
va_end(ap);
}
void pkg_info(FAR const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
pkg_vlog(stdout, "info", fmt, ap);
va_end(ap);
}

106
system/nxpkg/pkg_main.c Normal file
View file

@ -0,0 +1,106 @@
/****************************************************************************
* apps/system/nxpkg/pkg_main.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 <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pkg.h"
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char *argv[])
{
FAR const char *cmd;
if (argc < 2)
{
fprintf(stderr,
"Usage: %s <install|update|list|rollback|help> [args]\n",
argv[0]);
return EXIT_FAILURE;
}
cmd = argv[1];
if (strcmp(cmd, "help") == 0 || strcmp(cmd, "--help") == 0 ||
strcmp(cmd, "-h") == 0)
{
fprintf(stdout,
"Usage: %s <install|update|list|rollback|help> [args]\n",
argv[0]);
return EXIT_SUCCESS;
}
if (strcmp(cmd, "install") == 0)
{
if (argc != 3)
{
pkg_error("install expects exactly one package name");
fprintf(stderr,
"Usage: %s <install|update|list|rollback|help> [args]\n",
argv[0]);
return EXIT_FAILURE;
}
return pkg_install(argv[2]);
}
if (strcmp(cmd, "update") == 0)
{
pkg_error("'update' is not implemented yet in the current unit");
return EXIT_FAILURE;
}
if (strcmp(cmd, "list") == 0)
{
if (argc != 2)
{
pkg_error("list does not take additional arguments");
fprintf(stderr,
"Usage: %s <install|update|list|rollback|help> [args]\n",
argv[0]);
return EXIT_FAILURE;
}
return pkg_list(stdout);
}
if (strcmp(cmd, "rollback") == 0)
{
pkg_error("'rollback' is not implemented yet in the current unit");
return EXIT_FAILURE;
}
fprintf(stderr, "ERROR: Unknown subcommand '%s'\n", cmd);
fprintf(stderr,
"Usage: %s <install|update|list|rollback|help> [args]\n",
argv[0]);
return EXIT_FAILURE;
}

155
system/nxpkg/pkg_manifest.c Normal file
View file

@ -0,0 +1,155 @@
/****************************************************************************
* apps/system/nxpkg/pkg_manifest.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 <ctype.h>
#include <errno.h>
#include <string.h>
#include "pkg.h"
/****************************************************************************
* Private Functions
****************************************************************************/
static bool pkg_has_nonspace(FAR const char *value)
{
while (*value != '\0')
{
if (!isspace((unsigned char)*value))
{
return true;
}
value++;
}
return false;
}
static int pkg_validate_required(FAR const char *value)
{
if (value == NULL || value[0] == '\0' || !pkg_has_nonspace(value))
{
return -EINVAL;
}
return 0;
}
static bool pkg_validate_hex(FAR const char *value)
{
while (*value != '\0')
{
if (!isxdigit((unsigned char)*value))
{
return false;
}
value++;
}
return true;
}
/****************************************************************************
* Public Functions
****************************************************************************/
const char *pkg_manifest_type_str(enum pkg_payload_type_e type)
{
switch (type)
{
case PKG_PAYLOAD_ELF:
return "elf";
case PKG_PAYLOAD_SHARED_LIB:
return "shared-lib";
default:
return "unknown";
}
}
int pkg_manifest_validate(FAR const struct pkg_manifest_s *manifest)
{
if (manifest == NULL)
{
return -EINVAL;
}
if (pkg_validate_required(manifest->name) < 0 ||
pkg_validate_required(manifest->version) < 0 ||
pkg_validate_required(manifest->arch) < 0 ||
pkg_validate_required(manifest->compat) < 0 ||
pkg_validate_required(manifest->artifact) < 0 ||
pkg_validate_required(manifest->sha256) < 0)
{
return -EINVAL;
}
if (strlen(manifest->sha256) != PKG_HASH_HEX_LEN)
{
return -EINVAL;
}
if (!pkg_validate_hex(manifest->sha256))
{
return -EINVAL;
}
if (manifest->type != PKG_PAYLOAD_ELF &&
manifest->type != PKG_PAYLOAD_SHARED_LIB)
{
return -EINVAL;
}
return 0;
}
int pkg_manifest_parse_type(FAR const char *value,
FAR enum pkg_payload_type_e *type)
{
if (value == NULL || type == NULL)
{
return -EINVAL;
}
if (strcmp(value, "elf") == 0)
{
*type = PKG_PAYLOAD_ELF;
return 0;
}
if (strcmp(value, "shared-lib") == 0 ||
strcmp(value, "shared_lib") == 0 ||
strcmp(value, "shared") == 0)
{
*type = PKG_PAYLOAD_SHARED_LIB;
return 0;
}
return -EINVAL;
}

770
system/nxpkg/pkg_metadata.c Normal file
View file

@ -0,0 +1,770 @@
/****************************************************************************
* 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 <errno.h>
#include <stdlib.h>
#include <string.h>
#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;
}

501
system/nxpkg/pkg_store.c Normal file
View file

@ -0,0 +1,501 @@
/****************************************************************************
* apps/system/nxpkg/pkg_store.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 <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "pkg.h"
/****************************************************************************
* Private Functions
****************************************************************************/
static int pkg_store_format(FAR char *buffer, size_t size,
FAR const char *fmt,
FAR const char *name,
FAR const char *version)
{
int ret;
ret = snprintf(buffer, size, fmt, name, version);
if (ret < 0)
{
return ret;
}
if ((size_t)ret >= size)
{
return -ENAMETOOLONG;
}
return 0;
}
static int pkg_store_mkdir(FAR const char *path)
{
struct stat st;
int ret;
ret = stat(path, &st);
if (ret == 0)
{
if (!S_ISDIR(st.st_mode))
{
return -ENOTDIR;
}
return 0;
}
if (errno != ENOENT)
{
return -errno;
}
ret = mkdir(path, 0755);
if (ret < 0 && errno != EEXIST)
{
return -errno;
}
return 0;
}
static int pkg_store_mkdirs(FAR const char *path)
{
char buffer[PATH_MAX];
FAR char *cursor;
int ret;
ret = snprintf(buffer, sizeof(buffer), "%s", path);
if (ret < 0)
{
return ret;
}
if ((size_t)ret >= sizeof(buffer))
{
return -ENAMETOOLONG;
}
for (cursor = buffer + 1; *cursor != '\0'; cursor++)
{
if (*cursor != '/')
{
continue;
}
*cursor = '\0';
ret = pkg_store_mkdir(buffer);
*cursor = '/';
if (ret < 0)
{
return ret;
}
}
return pkg_store_mkdir(buffer);
}
static int pkg_store_write_all(int fd, FAR const char *buffer, size_t length)
{
size_t offset = 0;
while (offset < length)
{
ssize_t ret;
ret = write(fd, buffer + offset, length - offset);
if (ret < 0)
{
if (errno == EINTR)
{
continue;
}
return -errno;
}
offset += (size_t)ret;
}
return 0;
}
static FAR const char *pkg_store_basename(FAR const char *path)
{
FAR const char *leaf;
leaf = strrchr(path, '/');
return leaf != NULL ? leaf + 1 : path;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int pkg_store_prepare_layout(void)
{
int ret;
ret = pkg_store_mkdirs(PKG_REPO_DIR);
if (ret < 0)
{
return ret;
}
ret = pkg_store_mkdirs(PKG_STORE_DIR);
if (ret < 0)
{
return ret;
}
ret = pkg_store_mkdirs(PKG_TMP_DIR);
if (ret < 0)
{
return ret;
}
return pkg_store_mkdirs(PKG_TMP_PKG_DIR);
}
int pkg_store_ensure_package_root(FAR const char *name)
{
char path[PATH_MAX];
int ret;
ret = pkg_store_format_package_root(path, sizeof(path), name);
if (ret < 0)
{
return ret;
}
return pkg_store_mkdirs(path);
}
int pkg_store_ensure_version_dir(FAR const char *name,
FAR const char *version)
{
char path[PATH_MAX];
int ret;
ret = pkg_store_ensure_package_root(name);
if (ret < 0)
{
return ret;
}
ret = pkg_store_format_version_path(path, sizeof(path), name, version);
if (ret < 0)
{
return ret;
}
return pkg_store_mkdirs(path);
}
int pkg_store_format_index_path(FAR char *buffer, size_t size)
{
return pkg_store_format(buffer, size, "%s", PKG_REPO_INDEX, "");
}
int pkg_store_format_installed_path(FAR char *buffer, size_t size)
{
return pkg_store_format(buffer, size, "%s", PKG_REPO_INSTALLED, "");
}
int pkg_store_format_package_root(FAR char *buffer, size_t size,
FAR const char *name)
{
return pkg_store_format(buffer, size, PKG_STORE_DIR "/%s", name, "");
}
int pkg_store_format_version_path(FAR char *buffer, size_t size,
FAR const char *name,
FAR const char *version)
{
return pkg_store_format(buffer, size,
PKG_STORE_DIR "/%s/%s",
name, version);
}
int pkg_store_format_current_path(FAR char *buffer, size_t size,
FAR const char *name)
{
return pkg_store_format(buffer, size,
PKG_STORE_DIR "/%s/current",
name, "");
}
int pkg_store_format_previous_path(FAR char *buffer, size_t size,
FAR const char *name)
{
return pkg_store_format(buffer, size, PKG_STORE_DIR "/%s/previous", name,
"");
}
int pkg_store_format_txn_path(FAR char *buffer, size_t size,
FAR const char *name)
{
return pkg_store_format(buffer, size, PKG_STORE_DIR "/%s/.txn", name, "");
}
int pkg_store_format_lock_path(FAR char *buffer, size_t size,
FAR const char *name)
{
return pkg_store_format(buffer, size, PKG_STORE_DIR "/%s/.lock", name, "");
}
int pkg_store_format_download_path(FAR char *buffer, size_t size,
FAR const char *name,
FAR const char *version)
{
return pkg_store_format(buffer, size, PKG_TMP_PKG_DIR "/%s-%s.npkg", name,
version);
}
int pkg_store_format_payload_path(FAR char *buffer, size_t size,
FAR const char *name,
FAR const char *version,
FAR const char *artifact)
{
FAR const char *leaf;
int ret;
leaf = pkg_store_basename(artifact);
ret = snprintf(buffer, size, PKG_STORE_DIR "/%s/%s/%s",
name, version, leaf);
if (ret < 0)
{
return ret;
}
if ((size_t)ret >= size)
{
return -ENAMETOOLONG;
}
return 0;
}
int pkg_store_format_manifest_path(FAR char *buffer, size_t size,
FAR const char *name,
FAR const char *version)
{
return pkg_store_format(buffer, size, PKG_STORE_DIR "/%s/%s/manifest.json",
name, version);
}
int pkg_store_read_text(FAR const char *path, FAR char **buffer)
{
FAR FILE *stream;
FAR char *data;
long length;
size_t nread;
if (buffer == NULL)
{
return -EINVAL;
}
*buffer = NULL;
stream = fopen(path, "rb");
if (stream == NULL)
{
return errno == ENOENT ? -ENOENT : -errno;
}
if (fseek(stream, 0, SEEK_END) < 0)
{
fclose(stream);
return -errno;
}
length = ftell(stream);
if (length < 0)
{
fclose(stream);
return -errno;
}
if (fseek(stream, 0, SEEK_SET) < 0)
{
fclose(stream);
return -errno;
}
data = malloc((size_t)length + 1);
if (data == NULL)
{
fclose(stream);
return -ENOMEM;
}
nread = fread(data, 1, (size_t)length, stream);
if (nread != (size_t)length)
{
int err = ferror(stream);
fclose(stream);
free(data);
return err ? -EIO : -EINVAL;
}
fclose(stream);
data[length] = '\0';
*buffer = data;
return 0;
}
int pkg_store_write_text_atomic(FAR const char *path, FAR const char *text)
{
char tmp[PATH_MAX];
int fd;
int ret;
ret = snprintf(tmp, sizeof(tmp), "%s.tmp", path);
if (ret < 0)
{
return ret;
}
if ((size_t)ret >= sizeof(tmp))
{
return -ENAMETOOLONG;
}
fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0)
{
return -errno;
}
ret = pkg_store_write_all(fd, text, strlen(text));
if (ret < 0)
{
close(fd);
unlink(tmp);
return ret;
}
if (close(fd) < 0)
{
unlink(tmp);
return -errno;
}
if (rename(tmp, path) < 0)
{
unlink(tmp);
return -errno;
}
return 0;
}
int pkg_store_copy_file(FAR const char *src, FAR const char *dest)
{
int infd;
int outfd;
int ret;
char buffer[512];
infd = open(src, O_RDONLY);
if (infd < 0)
{
return -errno;
}
outfd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd < 0)
{
ret = -errno;
close(infd);
return ret;
}
for (; ; )
{
ssize_t nread;
nread = read(infd, buffer, sizeof(buffer));
if (nread < 0)
{
if (errno == EINTR)
{
continue;
}
ret = -errno;
goto errout;
}
if (nread == 0)
{
break;
}
ret = pkg_store_write_all(outfd, buffer, (size_t)nread);
if (ret < 0)
{
goto errout;
}
}
close(infd);
if (close(outfd) < 0)
{
unlink(dest);
return -errno;
}
return 0;
errout:
close(infd);
close(outfd);
unlink(dest);
return ret;
}
int pkg_store_remove_file(FAR const char *path)
{
if (unlink(path) < 0)
{
return errno == ENOENT ? 0 : -errno;
}
return 0;
}

110
system/nxpkg/pkg_txn.c Normal file
View file

@ -0,0 +1,110 @@
/****************************************************************************
* apps/system/nxpkg/pkg_txn.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 <errno.h>
#include <stdio.h>
#include "pkg.h"
/****************************************************************************
* Public Functions
****************************************************************************/
const char *pkg_txn_state_str(enum pkg_txn_state_e state)
{
switch (state)
{
case PKG_TXN_IDLE:
return "IDLE";
case PKG_TXN_FETCHING:
return "FETCHING";
case PKG_TXN_VERIFIED:
return "VERIFIED";
case PKG_TXN_STAGED:
return "STAGED";
case PKG_TXN_COMPAT_OK:
return "COMPAT_OK";
case PKG_TXN_ACTIVATED:
return "ACTIVATED";
case PKG_TXN_CLEANUP:
return "CLEANUP";
case PKG_TXN_FAILED:
return "FAILED";
case PKG_TXN_RESTORE:
return "RESTORE";
default:
return "UNKNOWN";
}
}
int pkg_txn_write_state(FAR const char *name, enum pkg_txn_state_e state)
{
char path[PATH_MAX];
char text[32];
int ret;
ret = pkg_store_format_txn_path(path, sizeof(path), name);
if (ret < 0)
{
return ret;
}
ret = snprintf(text, sizeof(text), "%s\n", pkg_txn_state_str(state));
if (ret < 0)
{
return ret;
}
if ((size_t)ret >= sizeof(text))
{
return -ENAMETOOLONG;
}
return pkg_store_write_text_atomic(path, text);
}
int pkg_txn_clear_state(FAR const char *name)
{
char path[PATH_MAX];
int ret;
ret = pkg_store_format_txn_path(path, sizeof(path), name);
if (ret < 0)
{
return ret;
}
return pkg_store_remove_file(path);
}