mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-01 20:29:00 +00:00
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:
parent
477d04b975
commit
65854ea805
15 changed files with 2920 additions and 0 deletions
43
system/nxpkg/CMakeLists.txt
Normal file
43
system/nxpkg/CMakeLists.txt
Normal 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
32
system/nxpkg/Kconfig
Normal 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
25
system/nxpkg/Make.defs
Normal 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
34
system/nxpkg/Makefile
Normal 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
187
system/nxpkg/pkg.h
Normal 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
64
system/nxpkg/pkg_compat.c
Normal 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
98
system/nxpkg/pkg_hash.c
Normal 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
501
system/nxpkg/pkg_install.c
Normal 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
65
system/nxpkg/pkg_log.c
Normal 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
106
system/nxpkg/pkg_main.c
Normal 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
155
system/nxpkg/pkg_manifest.c
Normal 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
770
system/nxpkg/pkg_metadata.c
Normal 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
501
system/nxpkg/pkg_store.c
Normal 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
110
system/nxpkg/pkg_txn.c
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue