mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-19 04:28:28 +00:00
system/nxpkg: complete the package lifecycle
Add the remaining package commands. Order database and payload updates so failures do not leave stale version pointers. Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
This commit is contained in:
parent
c4fd2b9b0e
commit
2fcb2dfbdd
2 changed files with 641 additions and 124 deletions
|
|
@ -25,10 +25,11 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pkg.h"
|
||||
|
|
@ -37,36 +38,9 @@
|
|||
* 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);
|
||||
|
|
@ -81,14 +55,58 @@ static int pkg_install_acquire_lock(FAR const char *name, FAR char *path,
|
|||
return ret;
|
||||
}
|
||||
|
||||
fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
|
||||
if (fd < 0)
|
||||
ret = pkg_lock_create(path);
|
||||
if (ret == -EEXIST)
|
||||
{
|
||||
return errno == EEXIST ? -EBUSY : -errno;
|
||||
pkg_reclaim_stale_lock(path);
|
||||
ret = pkg_lock_create(path);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
return ret == -EEXIST ? -EBUSY : ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: pkg_install_acquire_installed_lock
|
||||
*
|
||||
* Description:
|
||||
* Serialize installed database updates.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int pkg_install_acquire_installed_lock(FAR char *path, size_t size)
|
||||
{
|
||||
int ret;
|
||||
int tries;
|
||||
|
||||
ret = snprintf(path, size, PKG_ROOT_DIR "/instpkg.lk");
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((size_t)ret >= size)
|
||||
{
|
||||
return -ENAMETOOLONG;
|
||||
}
|
||||
|
||||
for (tries = 0; tries < 100; tries++)
|
||||
{
|
||||
ret = pkg_lock_create(path);
|
||||
if (ret == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ret != -EEXIST)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
pkg_reclaim_stale_lock(path);
|
||||
usleep(20 * 1000);
|
||||
}
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static bool pkg_install_has_version(
|
||||
|
|
@ -108,8 +126,49 @@ static bool pkg_install_has_version(
|
|||
return false;
|
||||
}
|
||||
|
||||
static int pkg_install_prune_oldest_version(
|
||||
FAR struct pkg_installed_entry_s *entry,
|
||||
FAR char *pruned_version, size_t pruned_version_size)
|
||||
{
|
||||
size_t victim = entry->version_count;
|
||||
size_t i;
|
||||
|
||||
/* Keep the current and rollback versions when pruning. */
|
||||
|
||||
for (i = 0; i < entry->version_count; i++)
|
||||
{
|
||||
if (strcmp(entry->versions[i], entry->current) != 0 &&
|
||||
strcmp(entry->versions[i], entry->previous) != 0)
|
||||
{
|
||||
victim = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (victim == entry->version_count)
|
||||
{
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
/* Let the caller delete this version after committing the database. */
|
||||
|
||||
snprintf(pruned_version, pruned_version_size, "%s",
|
||||
entry->versions[victim]);
|
||||
|
||||
for (i = victim; i + 1 < entry->version_count; i++)
|
||||
{
|
||||
memcpy(entry->versions[i], entry->versions[i + 1],
|
||||
sizeof(entry->versions[i]));
|
||||
}
|
||||
|
||||
entry->version_count--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pkg_install_add_version(FAR struct pkg_installed_entry_s *entry,
|
||||
FAR const char *version)
|
||||
FAR const char *version,
|
||||
FAR char *pruned_version,
|
||||
size_t pruned_version_size)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
|
@ -120,7 +179,12 @@ static int pkg_install_add_version(FAR struct pkg_installed_entry_s *entry,
|
|||
|
||||
if (entry->version_count >= PKG_INSTALLED_VERSIONS_MAX)
|
||||
{
|
||||
return -E2BIG;
|
||||
ret = pkg_install_prune_oldest_version(entry, pruned_version,
|
||||
pruned_version_size);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = snprintf(entry->versions[entry->version_count],
|
||||
|
|
@ -142,7 +206,9 @@ static int pkg_install_add_version(FAR struct pkg_installed_entry_s *entry,
|
|||
|
||||
static int pkg_install_update_installed(FAR struct pkg_installed_db_s *db,
|
||||
FAR const struct pkg_manifest_s
|
||||
*manifest)
|
||||
*manifest,
|
||||
FAR char *pruned_version,
|
||||
size_t pruned_version_size)
|
||||
{
|
||||
FAR struct pkg_installed_entry_s *entry;
|
||||
int ret;
|
||||
|
|
@ -196,12 +262,13 @@ static int pkg_install_update_installed(FAR struct pkg_installed_db_s *db,
|
|||
}
|
||||
|
||||
entry->type = manifest->type;
|
||||
return pkg_install_add_version(entry, manifest->version);
|
||||
return pkg_install_add_version(entry, manifest->version, pruned_version,
|
||||
pruned_version_size);
|
||||
}
|
||||
|
||||
static int pkg_install_write_pointers(
|
||||
FAR const struct pkg_installed_db_s *db,
|
||||
FAR const struct pkg_manifest_s *manifest)
|
||||
FAR const char *name)
|
||||
{
|
||||
FAR struct pkg_installed_entry_s *entry;
|
||||
char current[PATH_MAX];
|
||||
|
|
@ -209,21 +276,19 @@ static int pkg_install_write_pointers(
|
|||
int ret;
|
||||
|
||||
entry = pkg_metadata_find_installed((FAR struct pkg_installed_db_s *)db,
|
||||
manifest->name);
|
||||
name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
ret = pkg_store_format_current_path(current, sizeof(current),
|
||||
manifest->name);
|
||||
ret = pkg_store_format_current_path(current, PATH_MAX, name);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pkg_store_format_previous_path(previous, sizeof(previous),
|
||||
manifest->name);
|
||||
ret = pkg_store_format_previous_path(previous, PATH_MAX, name);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -247,185 +312,286 @@ 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] = "";
|
||||
FAR char *source;
|
||||
FAR char *tmp;
|
||||
FAR char *payload;
|
||||
FAR char *manifest_path;
|
||||
FAR char *lock;
|
||||
FAR char *installed_lock;
|
||||
FAR const char *artifact;
|
||||
char digest[PKG_HASH_HEX_LEN + 1];
|
||||
char pruned_version[PKG_VERSION_MAX + 1];
|
||||
bool staged_to_tmp;
|
||||
bool version_dir_created;
|
||||
bool installed_lock_held;
|
||||
int ret;
|
||||
|
||||
index = malloc(sizeof(*index));
|
||||
installed = malloc(sizeof(*installed));
|
||||
if (index == NULL || installed == NULL)
|
||||
pruned_version[0] = '\0';
|
||||
|
||||
index = pkg_zalloc(sizeof(*index));
|
||||
installed = pkg_zalloc(sizeof(*installed));
|
||||
source = pkg_malloc(PATH_MAX);
|
||||
tmp = pkg_malloc(PATH_MAX);
|
||||
payload = pkg_malloc(PATH_MAX);
|
||||
manifest_path = pkg_malloc(PATH_MAX);
|
||||
lock = pkg_malloc(PATH_MAX);
|
||||
installed_lock = pkg_malloc(PATH_MAX);
|
||||
if (index == NULL || installed == NULL || source == NULL || tmp == NULL ||
|
||||
payload == NULL || manifest_path == NULL || lock == NULL ||
|
||||
installed_lock == NULL)
|
||||
{
|
||||
free(index);
|
||||
free(installed);
|
||||
pkg_error("unable to allocate package metadata buffers");
|
||||
return EXIT_FAILURE;
|
||||
goto errout_early;
|
||||
}
|
||||
|
||||
source[0] = '\0';
|
||||
tmp[0] = '\0';
|
||||
payload[0] = '\0';
|
||||
manifest_path[0] = '\0';
|
||||
lock[0] = '\0';
|
||||
installed_lock[0] = '\0';
|
||||
installed_lock_held = false;
|
||||
artifact = NULL;
|
||||
staged_to_tmp = false;
|
||||
version_dir_created = false;
|
||||
|
||||
ret = pkg_store_prepare_layout();
|
||||
if (ret < 0)
|
||||
{
|
||||
free(index);
|
||||
free(installed);
|
||||
pkg_error("unable to prepare package layout: %d", ret);
|
||||
return EXIT_FAILURE;
|
||||
goto errout_early;
|
||||
}
|
||||
|
||||
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;
|
||||
goto errout_early;
|
||||
}
|
||||
|
||||
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;
|
||||
goto errout_early;
|
||||
}
|
||||
|
||||
ret = pkg_install_resolve_artifact(source, sizeof(source), manifest);
|
||||
ret = pkg_resolve_artifact_source(source, PATH_MAX, manifest);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("artifact path for '%s' is too long", name);
|
||||
return EXIT_FAILURE;
|
||||
pkg_error("unable to resolve artifact source for '%s': %d", name, ret);
|
||||
goto errout_early;
|
||||
}
|
||||
|
||||
ret = pkg_install_acquire_lock(name, lock, sizeof(lock));
|
||||
ret = pkg_install_acquire_lock(name, lock, PATH_MAX);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to acquire package lock for '%s': %d", name, ret);
|
||||
return EXIT_FAILURE;
|
||||
goto errout_early;
|
||||
}
|
||||
|
||||
ret = pkg_txn_write_state(name, PKG_TXN_FETCHING);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("txn state fetching failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_store_format_download_path(tmp, sizeof(tmp), manifest->name,
|
||||
manifest->version);
|
||||
if (ret < 0)
|
||||
if (pkg_source_is_url(source))
|
||||
{
|
||||
goto errout;
|
||||
ret = pkg_store_format_download_path(tmp, PATH_MAX, manifest->name,
|
||||
manifest->version);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("download path format failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_acquire_source(source, tmp);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("acquire source failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
artifact = tmp;
|
||||
staged_to_tmp = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
artifact = source;
|
||||
}
|
||||
|
||||
ret = pkg_store_copy_file(source, tmp);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_hash_file_sha256(tmp, digest);
|
||||
ret = pkg_hash_file_sha256(artifact, digest);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("sha256 failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
if (strcasecmp(digest, manifest->sha256) != 0)
|
||||
{
|
||||
ret = -EILSEQ;
|
||||
pkg_error("sha256 mismatch: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_txn_write_state(name, PKG_TXN_VERIFIED);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("txn state verified failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_store_format_version_path(payload, PATH_MAX, manifest->name,
|
||||
manifest->version);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("version path format failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
if (access(payload, F_OK) == 0)
|
||||
{
|
||||
version_dir_created = false;
|
||||
}
|
||||
else if (errno == ENOENT)
|
||||
{
|
||||
version_dir_created = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = -errno;
|
||||
pkg_error("unable to inspect version directory: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_store_ensure_version_dir(manifest->name, manifest->version);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("ensure version dir failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_store_format_payload_path(payload, sizeof(payload),
|
||||
ret = pkg_store_format_payload_path(payload, PATH_MAX,
|
||||
manifest->name, manifest->version,
|
||||
manifest->artifact);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("payload path format failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_store_copy_file(tmp, payload);
|
||||
ret = pkg_store_copy_file(artifact, payload);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("copy payload failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_store_format_manifest_path(manifest_path, sizeof(manifest_path),
|
||||
if (manifest->type == PKG_PAYLOAD_ELF &&
|
||||
chmod(payload, 0755) < 0 && errno != ENOSYS)
|
||||
{
|
||||
ret = -errno;
|
||||
pkg_error("mark payload executable failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_store_format_manifest_path(manifest_path, PATH_MAX,
|
||||
manifest->name, manifest->version);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("manifest path format failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_metadata_write_manifest(manifest_path, manifest);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("write manifest failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_txn_write_state(name, PKG_TXN_STAGED);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("txn state staged failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_compat_check(manifest);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("compat check failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_txn_write_state(name, PKG_TXN_COMPAT_OK);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("txn state compat_ok failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_install_acquire_installed_lock(installed_lock, PATH_MAX);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to acquire installed-db lock: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
installed_lock_held = true;
|
||||
|
||||
ret = pkg_metadata_load_installed(installed);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("load installed metadata failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_install_update_installed(installed, manifest);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_install_write_pointers(installed, manifest);
|
||||
ret = pkg_install_update_installed(installed, manifest, pruned_version,
|
||||
sizeof(pruned_version));
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("update installed metadata failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = pkg_metadata_save_installed(installed);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("save installed metadata failed: %d", ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
/* Remove the pruned payload after committing the database. */
|
||||
|
||||
if (pruned_version[0] != '\0')
|
||||
{
|
||||
pkg_store_remove_version_dir(manifest->name, pruned_version);
|
||||
}
|
||||
|
||||
ret = pkg_install_write_pointers(installed, manifest->name);
|
||||
if (ret < 0)
|
||||
{
|
||||
/* Pointer files can be rebuilt from the installed database. */
|
||||
|
||||
pkg_error("unable to refresh current/previous pointers: %d", ret);
|
||||
}
|
||||
|
||||
pkg_store_remove_file(installed_lock);
|
||||
installed_lock_held = false;
|
||||
|
||||
ret = pkg_txn_write_state(name, PKG_TXN_ACTIVATED);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
/* Do not remove payloads after the database commit. */
|
||||
|
||||
pkg_error("txn state activated failed: %d", ret);
|
||||
}
|
||||
|
||||
pkg_txn_write_state(name, PKG_TXN_CLEANUP);
|
||||
if (tmp[0] != '\0')
|
||||
if (staged_to_tmp && tmp[0] != '\0')
|
||||
{
|
||||
pkg_store_remove_file(tmp);
|
||||
}
|
||||
|
|
@ -437,27 +603,53 @@ int pkg_install(FAR const char *name)
|
|||
}
|
||||
|
||||
pkg_info("installed %s version %s", manifest->name, manifest->version);
|
||||
free(index);
|
||||
free(installed);
|
||||
return EXIT_SUCCESS;
|
||||
ret = EXIT_SUCCESS;
|
||||
goto freeout;
|
||||
|
||||
errout:
|
||||
pkg_txn_write_state(name, PKG_TXN_FAILED);
|
||||
if (tmp[0] != '\0')
|
||||
if (staged_to_tmp && tmp[0] != '\0')
|
||||
{
|
||||
pkg_store_remove_file(tmp);
|
||||
}
|
||||
|
||||
/* Remove a version directory created by this failed install. */
|
||||
|
||||
if (version_dir_created)
|
||||
{
|
||||
pkg_store_remove_version_dir(manifest->name, manifest->version);
|
||||
}
|
||||
|
||||
pkg_txn_clear_state(name);
|
||||
if (lock[0] != '\0')
|
||||
{
|
||||
pkg_lock_remove(lock);
|
||||
}
|
||||
|
||||
free(index);
|
||||
free(installed);
|
||||
if (installed_lock_held)
|
||||
{
|
||||
pkg_store_remove_file(installed_lock);
|
||||
}
|
||||
|
||||
pkg_error("install failed for '%s': %d", name, ret);
|
||||
return EXIT_FAILURE;
|
||||
goto freeout;
|
||||
|
||||
errout_early:
|
||||
ret = EXIT_FAILURE;
|
||||
|
||||
freeout:
|
||||
pkg_free(index);
|
||||
pkg_free(installed);
|
||||
pkg_free(source);
|
||||
pkg_free(tmp);
|
||||
pkg_free(payload);
|
||||
pkg_free(manifest_path);
|
||||
pkg_free(lock);
|
||||
pkg_free(installed_lock);
|
||||
|
||||
/* Preserve negative errno values for library callers. */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int pkg_list(FAR FILE *stream)
|
||||
|
|
@ -465,7 +657,7 @@ int pkg_list(FAR FILE *stream)
|
|||
FAR struct pkg_installed_db_s *db;
|
||||
int ret;
|
||||
|
||||
db = malloc(sizeof(*db));
|
||||
db = pkg_zalloc(sizeof(*db));
|
||||
if (db == NULL)
|
||||
{
|
||||
pkg_error("unable to allocate installed metadata buffer");
|
||||
|
|
@ -475,7 +667,7 @@ int pkg_list(FAR FILE *stream)
|
|||
ret = pkg_store_prepare_layout();
|
||||
if (ret < 0)
|
||||
{
|
||||
free(db);
|
||||
pkg_free(db);
|
||||
pkg_error("unable to prepare package layout: %d", ret);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
|
@ -483,7 +675,7 @@ int pkg_list(FAR FILE *stream)
|
|||
ret = pkg_metadata_load_installed(db);
|
||||
if (ret < 0)
|
||||
{
|
||||
free(db);
|
||||
pkg_free(db);
|
||||
pkg_error("unable to load installed metadata: %d", ret);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
|
@ -491,11 +683,279 @@ int pkg_list(FAR FILE *stream)
|
|||
ret = pkg_metadata_print_installed(stream, db);
|
||||
if (ret < 0)
|
||||
{
|
||||
free(db);
|
||||
pkg_free(db);
|
||||
pkg_error("unable to print installed metadata: %d", ret);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
free(db);
|
||||
pkg_free(db);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: pkg_uninstall
|
||||
*
|
||||
* Description:
|
||||
* Remove a package and all of its installed versions.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int pkg_uninstall(FAR const char *name)
|
||||
{
|
||||
FAR struct pkg_installed_db_s *db;
|
||||
FAR struct pkg_installed_entry_s *entry;
|
||||
struct pkg_installed_entry_s removed;
|
||||
char path[PATH_MAX];
|
||||
char package_lock[PATH_MAX];
|
||||
char installed_lock[PATH_MAX];
|
||||
size_t index;
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
if (!pkg_validate_path_component(name))
|
||||
{
|
||||
pkg_error("remove requires a valid package name");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
db = pkg_zalloc(sizeof(*db));
|
||||
if (db == NULL)
|
||||
{
|
||||
pkg_error("unable to allocate installed metadata buffer");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ret = pkg_store_prepare_layout();
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to prepare package layout: %d", ret);
|
||||
goto errout_with_db;
|
||||
}
|
||||
|
||||
ret = pkg_install_acquire_lock(name, package_lock, sizeof(package_lock));
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to acquire package lock for '%s': %d", name, ret);
|
||||
goto errout_with_db;
|
||||
}
|
||||
|
||||
ret = pkg_install_acquire_installed_lock(installed_lock,
|
||||
sizeof(installed_lock));
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to acquire installed-db lock: %d", ret);
|
||||
goto errout_with_package_lock;
|
||||
}
|
||||
|
||||
ret = pkg_metadata_load_installed(db);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to load installed metadata: %d", ret);
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
entry = pkg_metadata_find_installed(db, name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
pkg_error("package '%s' is not installed", name);
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
/* Commit removal before deleting payloads. */
|
||||
|
||||
removed = *entry;
|
||||
index = (size_t)(entry - db->entries);
|
||||
for (i = index; i + 1 < db->count; i++)
|
||||
{
|
||||
db->entries[i] = db->entries[i + 1];
|
||||
}
|
||||
|
||||
db->count--;
|
||||
|
||||
ret = pkg_metadata_save_installed(db);
|
||||
pkg_store_remove_file(installed_lock);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to save installed metadata: %d", ret);
|
||||
goto errout_with_package_lock;
|
||||
}
|
||||
|
||||
for (i = 0; i < removed.version_count; i++)
|
||||
{
|
||||
pkg_store_remove_version_dir(name, removed.versions[i]);
|
||||
}
|
||||
|
||||
if (pkg_store_format_txn_path(path, sizeof(path), name) == 0)
|
||||
{
|
||||
pkg_store_remove_file(path);
|
||||
}
|
||||
|
||||
if (pkg_store_format_current_path(path, sizeof(path), name) == 0)
|
||||
{
|
||||
pkg_store_remove_file(path);
|
||||
}
|
||||
|
||||
if (pkg_store_format_previous_path(path, sizeof(path), name) == 0)
|
||||
{
|
||||
pkg_store_remove_file(path);
|
||||
}
|
||||
|
||||
pkg_store_remove_file(package_lock);
|
||||
|
||||
if (pkg_store_format_package_root(path, sizeof(path), name) == 0)
|
||||
{
|
||||
rmdir(path);
|
||||
}
|
||||
|
||||
pkg_info("removed %s", name);
|
||||
pkg_free(db);
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
errout_with_installed_lock:
|
||||
pkg_store_remove_file(installed_lock);
|
||||
|
||||
errout_with_package_lock:
|
||||
pkg_store_remove_file(package_lock);
|
||||
|
||||
errout_with_db:
|
||||
pkg_free(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: pkg_rollback
|
||||
*
|
||||
* Description:
|
||||
* Swap the current and previous installed versions.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int pkg_rollback(FAR const char *name)
|
||||
{
|
||||
FAR struct pkg_installed_db_s *db;
|
||||
FAR struct pkg_installed_entry_s *entry;
|
||||
char version_path[PATH_MAX];
|
||||
char package_lock[PATH_MAX];
|
||||
char installed_lock[PATH_MAX];
|
||||
char swap[PKG_VERSION_MAX + 1];
|
||||
struct stat st;
|
||||
int ret;
|
||||
|
||||
if (!pkg_validate_path_component(name))
|
||||
{
|
||||
pkg_error("rollback requires a valid package name");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
db = pkg_zalloc(sizeof(*db));
|
||||
if (db == NULL)
|
||||
{
|
||||
pkg_error("unable to allocate installed metadata buffer");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ret = pkg_store_prepare_layout();
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to prepare package layout: %d", ret);
|
||||
goto errout_with_db;
|
||||
}
|
||||
|
||||
ret = pkg_install_acquire_lock(name, package_lock, sizeof(package_lock));
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to acquire package lock for '%s': %d", name, ret);
|
||||
goto errout_with_db;
|
||||
}
|
||||
|
||||
ret = pkg_install_acquire_installed_lock(installed_lock,
|
||||
sizeof(installed_lock));
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to acquire installed-db lock: %d", ret);
|
||||
goto errout_with_package_lock;
|
||||
}
|
||||
|
||||
ret = pkg_metadata_load_installed(db);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to load installed metadata: %d", ret);
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
entry = pkg_metadata_find_installed(db, name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
pkg_error("package '%s' is not installed", name);
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
if (entry->previous[0] == '\0')
|
||||
{
|
||||
pkg_error("package '%s' has no previous version to roll back to",
|
||||
name);
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
ret = pkg_store_format_version_path(version_path, sizeof(version_path),
|
||||
name, entry->previous);
|
||||
if (ret < 0 || stat(version_path, &st) < 0)
|
||||
{
|
||||
pkg_error("rollback target version '%s' is missing on disk",
|
||||
entry->previous);
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
ret = snprintf(swap, sizeof(swap), "%s", entry->current);
|
||||
if (ret < 0 || (size_t)ret >= sizeof(swap))
|
||||
{
|
||||
pkg_error("current version string too long to swap");
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
ret = snprintf(entry->current, sizeof(entry->current), "%s",
|
||||
entry->previous);
|
||||
if (ret < 0 || (size_t)ret >= sizeof(entry->current))
|
||||
{
|
||||
pkg_error("unable to update current version");
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
ret = snprintf(entry->previous, sizeof(entry->previous), "%s", swap);
|
||||
if (ret < 0 || (size_t)ret >= sizeof(entry->previous))
|
||||
{
|
||||
pkg_error("unable to update previous version");
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
/* Commit the database before refreshing pointer files. */
|
||||
|
||||
ret = pkg_metadata_save_installed(db);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to save installed metadata: %d", ret);
|
||||
goto errout_with_installed_lock;
|
||||
}
|
||||
|
||||
ret = pkg_install_write_pointers(db, name);
|
||||
pkg_store_remove_file(installed_lock);
|
||||
pkg_store_remove_file(package_lock);
|
||||
if (ret < 0)
|
||||
{
|
||||
pkg_error("unable to refresh current/previous pointers: %d", ret);
|
||||
}
|
||||
|
||||
pkg_info("rolled back %s to version %s", name, entry->current);
|
||||
pkg_free(db);
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
errout_with_installed_lock:
|
||||
pkg_store_remove_file(installed_lock);
|
||||
|
||||
errout_with_package_lock:
|
||||
pkg_store_remove_file(package_lock);
|
||||
|
||||
errout_with_db:
|
||||
pkg_free(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,18 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <netutils/cJSON.h>
|
||||
|
||||
#include "pkg.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define PKG_USAGE \
|
||||
"Usage: %s <install|update|remove|rollback|list|available|sync|help> " \
|
||||
"[args]\n"
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
|
@ -38,12 +48,15 @@
|
|||
int main(int argc, FAR char *argv[])
|
||||
{
|
||||
FAR const char *cmd;
|
||||
cJSON_Hooks hooks;
|
||||
|
||||
hooks.malloc_fn = malloc;
|
||||
hooks.free_fn = free;
|
||||
cJSON_InitHooks(&hooks);
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: %s <install|update|list|rollback|help> [args]\n",
|
||||
argv[0]);
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
|
@ -52,9 +65,7 @@ int main(int argc, FAR char *argv[])
|
|||
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]);
|
||||
fprintf(stdout, PKG_USAGE, argv[0]);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
@ -63,19 +74,51 @@ int main(int argc, FAR char *argv[])
|
|||
if (argc != 3)
|
||||
{
|
||||
pkg_error("install expects exactly one package name");
|
||||
fprintf(stderr,
|
||||
"Usage: %s <install|update|list|rollback|help> [args]\n",
|
||||
argv[0]);
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return pkg_install(argv[2]);
|
||||
/* Convert library errors to a shell exit status. */
|
||||
|
||||
return pkg_install(argv[2]) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Installing the latest indexed version also performs an update. */
|
||||
|
||||
if (strcmp(cmd, "update") == 0)
|
||||
{
|
||||
pkg_error("'update' is not implemented yet in the current unit");
|
||||
return EXIT_FAILURE;
|
||||
if (argc != 3)
|
||||
{
|
||||
pkg_error("update expects exactly one package name");
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return pkg_install(argv[2]) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "remove") == 0 || strcmp(cmd, "uninstall") == 0)
|
||||
{
|
||||
if (argc != 3)
|
||||
{
|
||||
pkg_error("remove expects exactly one package name");
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return pkg_uninstall(argv[2]);
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "rollback") == 0)
|
||||
{
|
||||
if (argc != 3)
|
||||
{
|
||||
pkg_error("rollback expects exactly one package name");
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return pkg_rollback(argv[2]);
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "list") == 0)
|
||||
|
|
@ -83,24 +126,38 @@ int main(int argc, FAR char *argv[])
|
|||
if (argc != 2)
|
||||
{
|
||||
pkg_error("list does not take additional arguments");
|
||||
fprintf(stderr,
|
||||
"Usage: %s <install|update|list|rollback|help> [args]\n",
|
||||
argv[0]);
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return pkg_list(stdout);
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "rollback") == 0)
|
||||
if (strcmp(cmd, "available") == 0)
|
||||
{
|
||||
pkg_error("'rollback' is not implemented yet in the current unit");
|
||||
return EXIT_FAILURE;
|
||||
if (argc != 2)
|
||||
{
|
||||
pkg_error("available does not take additional arguments");
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return pkg_available(stdout);
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "sync") == 0)
|
||||
{
|
||||
if (argc != 3)
|
||||
{
|
||||
pkg_error("sync expects exactly one index source");
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return pkg_sync(argv[2]) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
fprintf(stderr, "ERROR: Unknown subcommand '%s'\n", cmd);
|
||||
fprintf(stderr,
|
||||
"Usage: %s <install|update|list|rollback|help> [args]\n",
|
||||
argv[0]);
|
||||
fprintf(stderr, PKG_USAGE, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue