diff --git a/system/nxpkg/CMakeLists.txt b/system/nxpkg/CMakeLists.txt new file mode 100644 index 00000000000..8ac3e358532 --- /dev/null +++ b/system/nxpkg/CMakeLists.txt @@ -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() diff --git a/system/nxpkg/Kconfig b/system/nxpkg/Kconfig new file mode 100644 index 00000000000..49bc9dc3596 --- /dev/null +++ b/system/nxpkg/Kconfig @@ -0,0 +1,31 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config SYSTEM_NXPKG + tristate "'nxpkg' lifecycle helper" + default n + 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 diff --git a/system/nxpkg/Make.defs b/system/nxpkg/Make.defs new file mode 100644 index 00000000000..bdb799080fe --- /dev/null +++ b/system/nxpkg/Make.defs @@ -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 diff --git a/system/nxpkg/Makefile b/system/nxpkg/Makefile new file mode 100644 index 00000000000..757f9fc896e --- /dev/null +++ b/system/nxpkg/Makefile @@ -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 diff --git a/system/nxpkg/pkg.h b/system/nxpkg/pkg.h new file mode 100644 index 00000000000..39a4bf0c21d --- /dev/null +++ b/system/nxpkg/pkg.h @@ -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 +#include +#include +#include + +/**************************************************************************** + * 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 */ diff --git a/system/nxpkg/pkg_compat.c b/system/nxpkg/pkg_compat.c new file mode 100644 index 00000000000..301fb07aacf --- /dev/null +++ b/system/nxpkg/pkg_compat.c @@ -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 +#include + +#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; +} diff --git a/system/nxpkg/pkg_hash.c b/system/nxpkg/pkg_hash.c new file mode 100644 index 00000000000..40965067df5 --- /dev/null +++ b/system/nxpkg/pkg_hash.c @@ -0,0 +1,313 @@ +/**************************************************************************** + * 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 +#include +#include +#include + +#include "pkg.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct pkg_sha256_s +{ + uint32_t state[8]; + uint64_t bitlen; + uint8_t block[64]; + size_t used; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const uint32_t g_pkg_sha256_k[64] = +{ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint32_t pkg_rotr32(uint32_t value, uint32_t shift) +{ + return (value >> shift) | (value << (32 - shift)); +} + +static uint32_t pkg_load_be32(FAR const uint8_t *buffer) +{ + return ((uint32_t)buffer[0] << 24) | + ((uint32_t)buffer[1] << 16) | + ((uint32_t)buffer[2] << 8) | + (uint32_t)buffer[3]; +} + +static void pkg_store_be32(FAR uint8_t *buffer, uint32_t value) +{ + buffer[0] = (uint8_t)(value >> 24); + buffer[1] = (uint8_t)(value >> 16); + buffer[2] = (uint8_t)(value >> 8); + buffer[3] = (uint8_t)value; +} + +static void pkg_store_be64(FAR uint8_t *buffer, uint64_t value) +{ + int i; + + for (i = 7; i >= 0; i--) + { + buffer[i] = (uint8_t)value; + value >>= 8; + } +} + +static void pkg_sha256_transform(FAR struct pkg_sha256_s *ctx, + FAR const uint8_t *block) +{ + uint32_t w[64]; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint32_t f; + uint32_t g; + uint32_t h; + int i; + + for (i = 0; i < 16; i++) + { + w[i] = pkg_load_be32(block + i * 4); + } + + for (i = 16; i < 64; i++) + { + uint32_t s0; + uint32_t s1; + + s0 = pkg_rotr32(w[i - 15], 7) ^ pkg_rotr32(w[i - 15], 18) ^ + (w[i - 15] >> 3); + s1 = pkg_rotr32(w[i - 2], 17) ^ pkg_rotr32(w[i - 2], 19) ^ + (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; i++) + { + uint32_t s1; + uint32_t ch; + uint32_t temp1; + uint32_t s0; + uint32_t maj; + uint32_t temp2; + + s1 = pkg_rotr32(e, 6) ^ pkg_rotr32(e, 11) ^ pkg_rotr32(e, 25); + ch = (e & f) ^ ((~e) & g); + temp1 = h + s1 + ch + g_pkg_sha256_k[i] + w[i]; + s0 = pkg_rotr32(a, 2) ^ pkg_rotr32(a, 13) ^ pkg_rotr32(a, 22); + maj = (a & b) ^ (a & c) ^ (b & c); + temp2 = s0 + maj; + + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +static void pkg_sha256_init(FAR struct pkg_sha256_s *ctx) +{ + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; + ctx->bitlen = 0; + ctx->used = 0; +} + +static void pkg_sha256_update(FAR struct pkg_sha256_s *ctx, + FAR const uint8_t *data, size_t length) +{ + while (length > 0) + { + size_t remaining; + size_t tocopy; + + remaining = sizeof(ctx->block) - ctx->used; + tocopy = length < remaining ? length : remaining; + + memcpy(ctx->block + ctx->used, data, tocopy); + ctx->used += tocopy; + ctx->bitlen += (uint64_t)tocopy * 8; + data += tocopy; + length -= tocopy; + + if (ctx->used == sizeof(ctx->block)) + { + pkg_sha256_transform(ctx, ctx->block); + ctx->used = 0; + } + } +} + +static void pkg_sha256_final(FAR struct pkg_sha256_s *ctx, + FAR uint8_t digest[32]) +{ + size_t i; + + ctx->block[ctx->used++] = 0x80; + + if (ctx->used > 56) + { + while (ctx->used < 64) + { + ctx->block[ctx->used++] = 0; + } + + pkg_sha256_transform(ctx, ctx->block); + ctx->used = 0; + } + + while (ctx->used < 56) + { + ctx->block[ctx->used++] = 0; + } + + pkg_store_be64(ctx->block + 56, ctx->bitlen); + pkg_sha256_transform(ctx, ctx->block); + + for (i = 0; i < 8; i++) + { + pkg_store_be32(digest + i * 4, ctx->state[i]); + } +} + +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]) +{ + struct pkg_sha256_s ctx; + FAR FILE *stream; + uint8_t raw[32]; + uint8_t buffer[512]; + size_t nread; + + stream = fopen(path, "rb"); + if (stream == NULL) + { + return -errno; + } + + pkg_sha256_init(&ctx); + + for (; ; ) + { + nread = fread(buffer, 1, sizeof(buffer), stream); + if (nread > 0) + { + pkg_sha256_update(&ctx, buffer, nread); + } + + if (nread < sizeof(buffer)) + { + if (ferror(stream)) + { + fclose(stream); + return -EIO; + } + + break; + } + } + + fclose(stream); + pkg_sha256_final(&ctx, raw); + pkg_hex_encode(raw, sizeof(raw), digest); + return 0; +} diff --git a/system/nxpkg/pkg_install.c b/system/nxpkg/pkg_install.c new file mode 100644 index 00000000000..12338b068d3 --- /dev/null +++ b/system/nxpkg/pkg_install.c @@ -0,0 +1,493 @@ +/**************************************************************************** + * 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 +#include +#include +#include +#include +#include + +#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; +} diff --git a/system/nxpkg/pkg_log.c b/system/nxpkg/pkg_log.c new file mode 100644 index 00000000000..bed06b13ffb --- /dev/null +++ b/system/nxpkg/pkg_log.c @@ -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 +#include + +#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); +} diff --git a/system/nxpkg/pkg_main.c b/system/nxpkg/pkg_main.c new file mode 100644 index 00000000000..c049592c5b6 --- /dev/null +++ b/system/nxpkg/pkg_main.c @@ -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 +#include +#include +#include + +#include "pkg.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + FAR const char *cmd; + + if (argc < 2) + { + fprintf(stderr, + "Usage: %s [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 [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 [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 [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 [args]\n", + argv[0]); + return EXIT_FAILURE; +} diff --git a/system/nxpkg/pkg_manifest.c b/system/nxpkg/pkg_manifest.c new file mode 100644 index 00000000000..3e7b56a2637 --- /dev/null +++ b/system/nxpkg/pkg_manifest.c @@ -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 +#include +#include + +#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; +} diff --git a/system/nxpkg/pkg_metadata.c b/system/nxpkg/pkg_metadata.c new file mode 100644 index 00000000000..d79d290b6a7 --- /dev/null +++ b/system/nxpkg/pkg_metadata.c @@ -0,0 +1,694 @@ +/**************************************************************************** + * 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 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 = strcmp(lhs, rhs); + 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 strcmp(lhs, rhs); + } + + leftbuf[leftlen] = lhs[leftlen]; + leftlen++; + } + + while (rhs[rightlen] != '\0' && rhs[rightlen] != '.') + { + if (rightlen + 1 >= sizeof(rightbuf)) + { + return strcmp(lhs, rhs); + } + + 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; + 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; + } + + ret = pkg_store_read_text(path, &text); + 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_INDEX_MAX) + { + cJSON_Delete(root); + return -E2BIG; + } + + ret = pkg_metadata_parse_manifest(item, &index->manifests[count]); + if (ret < 0) + { + cJSON_Delete(root); + return ret; + } + + 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; + size_t i; + + if (index == NULL || name == NULL) + { + return NULL; + } + + for (i = 0; i < index->count; i++) + { + FAR const struct pkg_manifest_s *candidate = &index->manifests[i]; + + if (strcmp(candidate->name, name) != 0) + { + 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 (strcmp(db->entries[i].name, name) == 0) + { + 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; +} diff --git a/system/nxpkg/pkg_store.c b/system/nxpkg/pkg_store.c new file mode 100644 index 00000000000..84680ef3a12 --- /dev/null +++ b/system/nxpkg/pkg_store.c @@ -0,0 +1,496 @@ +/**************************************************************************** + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/system/nxpkg/pkg_txn.c b/system/nxpkg/pkg_txn.c new file mode 100644 index 00000000000..e20a8700f44 --- /dev/null +++ b/system/nxpkg/pkg_txn.c @@ -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 +#include + +#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); +}