This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git


The following commit(s) were added to refs/heads/master by this push:
     new 2fcb2dfbd system/nxpkg: complete the package lifecycle
2fcb2dfbd is described below

commit 2fcb2dfbdd278ad88e303be9c86606ac32c4b77a
Author: aviralgarg05 <[email protected]>
AuthorDate: Sat Aug 15 16:03:15 2026 +0530

    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 <[email protected]>
---
 system/nxpkg/pkg_install.c | 640 ++++++++++++++++++++++++++++++++++++++-------
 system/nxpkg/pkg_main.c    |  99 +++++--
 2 files changed, 628 insertions(+), 111 deletions(-)

diff --git a/system/nxpkg/pkg_install.c b/system/nxpkg/pkg_install.c
index b9b095597..db6e57597 100644
--- a/system/nxpkg/pkg_install.c
+++ b/system/nxpkg/pkg_install.c
@@ -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,58 +38,75 @@
  * Private Functions
  ****************************************************************************/
 
-static int pkg_install_resolve_artifact(FAR char *buffer, size_t size,
-                                        FAR const struct pkg_manifest_s
-                                          *manifest)
+static int pkg_install_acquire_lock(FAR const char *name, FAR char *path,
+                                    size_t size)
 {
   int ret;
 
-  if (manifest->artifact[0] == '/')
+  ret = pkg_store_ensure_package_root(name);
+  if (ret < 0)
     {
-      ret = snprintf(buffer, size, "%s", manifest->artifact);
-      if (ret < 0)
-        {
-          return ret;
-        }
-
-      return (size_t)ret >= size ? -ENAMETOOLONG : 0;
+      return ret;
     }
 
-  ret = snprintf(buffer, size, "%s/%s", PKG_REPO_DIR, manifest->artifact);
+  ret = pkg_store_format_lock_path(path, size, name);
   if (ret < 0)
     {
       return ret;
     }
 
-  return (size_t)ret >= size ? -ENAMETOOLONG : 0;
+  ret = pkg_lock_create(path);
+  if (ret == -EEXIST)
+    {
+      pkg_reclaim_stale_lock(path);
+      ret = pkg_lock_create(path);
+    }
+
+  return ret == -EEXIST ? -EBUSY : ret;
 }
 
-static int pkg_install_acquire_lock(FAR const char *name, FAR char *path,
-                                    size_t size)
+/****************************************************************************
+ * 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 fd;
   int ret;
+  int tries;
 
-  ret = pkg_store_ensure_package_root(name);
+  ret = snprintf(path, size, PKG_ROOT_DIR "/instpkg.lk");
   if (ret < 0)
     {
       return ret;
     }
 
-  ret = pkg_store_format_lock_path(path, size, name);
-  if (ret < 0)
+  if ((size_t)ret >= size)
     {
-      return ret;
+      return -ENAMETOOLONG;
     }
 
-  fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
-  if (fd < 0)
+  for (tries = 0; tries < 100; tries++)
     {
-      return errno == EEXIST ? -EBUSY : -errno;
+      ret = pkg_lock_create(path);
+      if (ret == 0)
+        {
+          return 0;
+        }
+
+      if (ret != -EEXIST)
+        {
+          return ret;
+        }
+
+      pkg_reclaim_stale_lock(path);
+      usleep(20 * 1000);
     }
 
-  close(fd);
-  return 0;
+  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_store_copy_file(source, tmp);
-  if (ret < 0)
+      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
     {
-      goto errout;
+      artifact = source;
     }
 
-  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;
+    }
+
+  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, sizeof(manifest_path),
+  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_metadata_load_installed(installed);
+  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;
     }
 
-  ret = pkg_install_update_installed(installed, manifest);
+  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_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;
 }
diff --git a/system/nxpkg/pkg_main.c b/system/nxpkg/pkg_main.c
index c049592c5..3755e1231 100644
--- a/system/nxpkg/pkg_main.c
+++ b/system/nxpkg/pkg_main.c
@@ -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;
 }

Reply via email to