]> git.ipfire.org Git - thirdparty/libsolv.git/commitdiff
Implement apkv3 support
authorMichael Schroeder <mls@suse.de>
Wed, 15 Jan 2025 10:46:24 +0000 (11:46 +0100)
committerMichael Schroeder <mls@suse.de>
Wed, 15 Jan 2025 10:46:24 +0000 (11:46 +0100)
CMakeLists.txt
ext/CMakeLists.txt
ext/repo_apk.c
ext/repo_apkv3.c [new file with mode: 0644]
ext/repo_apkv3.h [new file with mode: 0644]

index 30125cc9c32194f5c01fdeaafa61b7d91835fc5a..e344222c7f52ad25e20bc3b6bc0a7228cdaf75e6 100644 (file)
@@ -156,6 +156,10 @@ IF (ENABLE_ZCHUNK_COMPRESSION)
 SET (ENABLE_ZSTD_COMPRESSION ON)
 ENDIF (ENABLE_ZCHUNK_COMPRESSION)
 
+IF (ENABLE_APK)
+SET (ENABLE_ZSTD_COMPRESSION ON)
+ENDIF (ENABLE_APK)
+
 IF (ENABLE_RPMMD OR ENABLE_SUSEREPO OR ENABLE_APPDATA OR ENABLE_COMPS OR ENABLE_HELIXREPO OR ENABLE_MDKREPO)
 IF (WITH_LIBXML2 )
 FIND_PACKAGE (LibXml2 REQUIRED)
index 4b36934a89a7739c5694513656973d5c8f10d6e8..637d67fe79fc6b9075e94f53ec055c9a0a496972 100644 (file)
@@ -76,7 +76,7 @@ ENDIF (ENABLE_DEBIAN)
 
 IF (ENABLE_APK)
     SET (libsolvext_SRCS ${libsolvext_SRCS}
-       repo_apk.c)
+       repo_apk.c repo_apkv3.c)
     SET (libsolvext_HEADERS ${libsolvext_HEADERS}
        repo_apk.h)
 ENDIF (ENABLE_APK)
index f174368687852e7658d0f0ec7333af507aa8610c..223490a221f28ab0fee8a5ef77bf06b07f7a18a4 100644 (file)
@@ -15,7 +15,7 @@
 #include <fcntl.h>
 
 #include <zlib.h>
-
+#include <zstd.h>
 
 #include "pool.h"
 #include "repo.h"
@@ -24,7 +24,9 @@
 #include "solv_xfopen.h"
 #include "tarhead.h"
 #include "repo_apk.h"
+#include "repo_apkv3.h"
 
+/* zlib decompression */
 
 struct zstream {
   int fd;
@@ -38,13 +40,13 @@ struct zstream {
 };
 
 static struct zstream *
-apkz_open(int fd)
+apkz_open(int fd, int raw)
 {
   struct zstream *zstream = solv_calloc(1, sizeof(*zstream));
   zstream->fd = fd;
-  if (inflateInit2(&zstream->zs, 15 + 32) != Z_OK)     /* 32: enable gzip */
+  if (inflateInit2(&zstream->zs, raw ? -15 : 15 + 32) != Z_OK) /* 32: enable gzip */
     {
-      solv_free(&zstream->zs);
+      solv_free(zstream);
       return 0;
     }
   return zstream;
@@ -144,6 +146,195 @@ apkz_read(void *cookie, char *buf, size_t len)
     }
 }
 
+/* zstd decompression */
+
+struct zstdstream {
+  int fd;
+  FILE *fp;
+  int eof;
+  ZSTD_DCtx *ctx;
+  ZSTD_inBuffer in;
+  unsigned char buf[65536];
+};
+
+static struct zstdstream *
+apkzstd_open(int fd)
+{
+  struct zstdstream *zstdstream = solv_calloc(1, sizeof(*zstdstream));
+  zstdstream->fd = fd;
+  zstdstream->in.src = zstdstream->buf;
+  zstdstream->in.size = zstdstream->in.pos = 0;
+  if (!(zstdstream->ctx =  ZSTD_createDCtx()))
+    {
+      solv_free(zstdstream);
+      return 0;
+    }
+  return zstdstream;
+}
+
+static int
+apkzstd_close(void *cookie)
+{
+  struct zstdstream *zstdstream = cookie;
+  ZSTD_freeDCtx(zstdstream->ctx);
+  if (zstdstream->fd != -1)
+    close(zstdstream->fd);
+  solv_free(zstdstream);
+  return 0;
+}
+
+static inline ssize_t
+apkzstd_fillbuf(struct zstdstream *zstdstream)
+{
+  ssize_t rr;
+  if (zstdstream->fp)
+    {
+      rr = fread(zstdstream->buf, 1, sizeof(zstdstream->buf), zstdstream->fp);
+      if (rr <= 0 && ferror(zstdstream->fp))
+       rr = -1;
+    }
+  else
+    rr = read(zstdstream->fd, zstdstream->buf, sizeof(zstdstream->buf));
+  if (rr >= 0)
+    {
+      zstdstream->in.pos = 0;
+      zstdstream->in.size = rr;
+    }
+  return rr;
+}
+
+static ssize_t
+apkzstd_read(void *cookie, char *buf, size_t len)
+{
+  struct zstdstream *zstdstream = cookie;
+  ZSTD_outBuffer out;
+  int eof = 0;
+  size_t r;
+
+  if (!zstdstream)
+    return -1;
+  if (zstdstream->eof)
+    return 0;
+  out.dst = buf;
+  out.pos = 0;
+  out.size = len;
+  for (;;)
+    {
+      if (zstdstream->in.pos >= zstdstream->in.size)
+       {
+         ssize_t rr = apkzstd_fillbuf(zstdstream);
+         if (rr < 0)
+           return rr;
+         if (rr == 0)
+           eof = 1;
+       }
+      r = ZSTD_decompressStream(zstdstream->ctx, &out, &zstdstream->in);
+      if (ZSTD_isError(r))
+       return -1;
+      if (out.pos == out.size || eof)
+       return out.pos;
+    }
+}
+
+
+/* apkv3 handling */
+
+static FILE *
+open_apkv3_error(Pool *pool, int fd, const char *fn, const char *msg)
+{
+  pool_error(pool, -1, "%s: %s", fn, msg);
+  if (fd != -1)
+    close(fd);
+  return 0;
+}
+
+static FILE *
+open_apkv3(Pool *pool, int fd, FILE *fp, const char *fn, int adbchar)
+{
+  unsigned char comp[2];
+  char buf[4];
+  FILE *cfp;
+
+  comp[0] = comp[1] = 0;
+  if (adbchar == 'c')
+    {
+      ssize_t r;
+      if (fp)
+       r = fread(comp, 2, 1, fp) == 1 ? 2 : feof(fp) ? 0 : -1;
+      else
+       r = read(fd, comp, 2);
+      if (r != 2)
+       return open_apkv3_error(pool, fd, fn, "compression header read error");
+    }
+  else if (adbchar == 'd')
+    comp[0] = 1;
+  else if (adbchar != '.')
+    return open_apkv3_error(pool, fd, fn, "not an apkv3 file");
+  if (comp[0] == 0)
+    cfp = fp ? fp : fdopen(fd, "r");
+  else if (comp[0] == 1)
+    {
+      struct zstream *zstream = apkz_open(fd, 1);
+      if (!zstream)
+       return open_apkv3_error(pool, fd, fn, "zstream setup error");
+      if (fp)
+       zstream->fp = fp;
+      if ((cfp = solv_cookieopen(zstream, "r", apkz_read, 0, apkz_close)) == 0)
+        return open_apkv3_error(pool, fd, fn, "zstream cookie setup error");
+    }
+  else if (comp[0] == 2)
+    {
+      struct zstdstream *zstdstream = apkzstd_open(fd);
+      if (!zstdstream)
+       return open_apkv3_error(pool, fd, fn, "zstdstream setup error");
+      if (fp)
+       zstdstream->fp = fp;
+      if ((cfp = solv_cookieopen(zstdstream, "r", apkzstd_read, 0, apkzstd_close)) == 0)
+       return open_apkv3_error(pool, fd, fn, "zstdstream cookie setup error");
+    }
+  else
+    return open_apkv3_error(pool, fd, fn, "unsupported apkv3 compression");
+
+  if (adbchar != '.')
+    {
+      if (fread(buf, 4, 1, cfp) != 1 || buf[0] != 'A' || buf[1] != 'D' || buf[2] != 'B' || buf[3] != '.')
+       {
+         pool_error(pool, -1, "%s: not an apkv3 file", fn);
+         if (cfp != fp)
+           fclose(cfp);
+         return 0;
+       }
+    }
+  return cfp;
+}
+
+static Id
+add_apkv3_pkg(Repo *repo, Repodata *data, const char *fn, int flags, int fd, int adbchar)
+{
+  FILE *fp;
+  Id p;
+  if (!(fp = open_apkv3(repo->pool, fd, 0, fn, adbchar)))
+    return 0;
+  p = apkv3_add_pkg(repo, data, fn, fp, flags);
+  fclose(fp);
+  return p;
+}
+
+static int
+add_apkv3_idx(Repo *repo, Repodata *data, FILE *fp, int flags, int adbchar)
+{
+  FILE *cfp;
+  int r;
+  if (!(cfp = open_apkv3(repo->pool, -1, fp, (flags & APK_ADD_INDEX ? "installed db" : "package index"), adbchar)))
+    return -1;
+  r = apkv3_add_idx(repo, data, cfp, flags);
+  fclose(cfp);
+  return r;
+}
+
+
+/* apkv2 handling */
+
 static void
 add_deps(Repo *repo, Solvable *s, Id what, char *p)
 {
@@ -215,6 +406,7 @@ repo_add_apk_pkg(Repo *repo, const char *fn, int flags)
   char *line = 0;
   size_t l, line_alloc = 0;
   int haveorigin = 0;
+  char first[4];
 
   data = repo_add_repodata(repo, flags);
   if ((fd = open(flags & REPO_USE_ROOTDIR ? pool_prepend_rootdir_tmp(pool, fn) : fn, O_RDONLY)) == -1)
@@ -222,7 +414,15 @@ repo_add_apk_pkg(Repo *repo, const char *fn, int flags)
       pool_error(pool, -1, "%s: %s", fn, strerror(errno));
       return 0;
     }
-  zstream = apkz_open(fd);
+  if (read(fd, first, 4) == 4 && first[0] == 'A' && first[1] == 'D' && first[2] == 'B')
+    return add_apkv3_pkg(repo, data, fn, flags, fd, first[3]);
+  if (lseek(fd, 0, SEEK_SET)) 
+    {
+      pool_error(pool, -1, "%s: lseek: %s", fn, strerror(errno));
+      close(fd);
+      return 0;
+    }
+  zstream = apkz_open(fd, 0);
   if (!zstream)
     {
       pool_error(pool, -1, "%s: %s", fn, strerror(errno));
@@ -245,7 +445,7 @@ repo_add_apk_pkg(Repo *repo, const char *fn, int flags)
       fclose(fp);
       return 0;
     }
-  if (flags & APK_ADD_WITH_HDRID)
+  if ((flags & APK_ADD_WITH_HDRID) != 0)
     {
       q1chk = solv_chksum_create(REPOKEY_TYPE_SHA1);
       zstream->readcb_data = q1chk;
@@ -297,7 +497,7 @@ repo_add_apk_pkg(Repo *repo, const char *fn, int flags)
          else if (!strncmp(line, "arch = ", 7))
            s->arch = pool_str2id(pool, line + 7, 1);
          else if (!strncmp(line, "license = ", 10))
-           repodata_add_poolstr_array(data, s - pool->solvables, SOLVABLE_LICENSE, line + 10);
+           repodata_set_poolstr(data, s - pool->solvables, SOLVABLE_LICENSE, line + 10);
          else if (!strncmp(line, "origin = ", 9))
            {
              if (s->name && !strcmp(line + 9,  pool_id2str(pool, s->name)))
@@ -403,7 +603,7 @@ apk_add_hdrid(Repodata *data, Id p, char *idstr)
 }
 
 static void
-apk_process_index(Repo *repo, Repodata *data, struct tarhead *th)
+apk_process_index(Repo *repo, Repodata *data, struct tarhead *th, int flags)
 {
   Pool *pool = repo->pool;
   Solvable *s = 0;
@@ -464,7 +664,7 @@ apk_process_index(Repo *repo, Repodata *data, struct tarhead *th)
       else if (line[0] == 'A')
        s->arch = pool_str2id(pool, line + 2, 1);
       else if (line[0] == 'L')
-       repodata_add_poolstr_array(data, s - pool->solvables, SOLVABLE_LICENSE, line + 2);
+       repodata_set_poolstr(data, s - pool->solvables, SOLVABLE_LICENSE, line + 2);
       else if (line[0] == 'o')
        {
          if (s->name && !strcmp(line + 2,  pool_id2str(pool, s->name)))
@@ -479,7 +679,7 @@ apk_process_index(Repo *repo, Repodata *data, struct tarhead *th)
        add_deps(repo, s, SOLVABLE_PROVIDES, line + 2);
       else if (line[0] == 'i')
        add_deps(repo, s, SOLVABLE_SUPPLEMENTS, line + 2);
-      else if (line[0] == 'C')
+      else if (line[0] == 'C' && (flags & APK_ADD_WITH_HDRID) != 0)
        apk_add_hdrid(data, s - pool->solvables, line + 2);
     }
   solv_free(line);
@@ -488,6 +688,7 @@ apk_process_index(Repo *repo, Repodata *data, struct tarhead *th)
 int
 repo_add_apk_repo(Repo *repo, FILE *fp, int flags)
 {
+  char first[4];
   struct tarhead th;
   Repodata *data;
   int c;
@@ -501,11 +702,21 @@ repo_add_apk_repo(Repo *repo, FILE *fp, int flags)
     return (flags & APK_ADD_INDEX) != 0 ? 0 : -1;      /* an empty file is allowed for the v2 index */
   ungetc(c, fp);
 
+  if (c == 'A')
+    {
+     if (fread(first, 4, 1, fp) != 1)
+       return -1;
+     if (first[0] == 'A' && first[1] == 'D' && first[2] == 'B')
+       return add_apkv3_idx(repo, data, fp, flags, first[3]);
+     if ((flags & APK_ADD_INDEX) == 0)
+      return -1;       /* not a tar file */
+    }
+
   if (c == 0x1f)
     {
       struct zstream *zstream;
       /* gzip compressed, setup decompression */
-      zstream = apkz_open(-1);
+      zstream = apkz_open(-1, 0);
       if (!zstream)
        return -1;
       zstream->fp = fp;
@@ -519,9 +730,15 @@ repo_add_apk_repo(Repo *repo, FILE *fp, int flags)
     }
 
   tarhead_init(&th, fp);
+  if (c == 'A')
+    {
+      /* initialize input buffer with 4 bytes we already read */
+      memcpy(th.blk, first, 4);
+      th.end = 4;
+    }
   
   if ((flags & APK_ADD_INDEX) != 0)
-    apk_process_index(repo, data, &th);
+    apk_process_index(repo, data, &th, flags);
   else
     {
       while (tarhead_next(&th) > 0)
@@ -529,7 +746,7 @@ repo_add_apk_repo(Repo *repo, FILE *fp, int flags)
          if (th.type != 1 || strcmp(th.path, "APKINDEX") != 0)
            tarhead_skip(&th);
          else
-           apk_process_index(repo, data, &th);
+           apk_process_index(repo, data, &th, flags);
        }
     }
   tarhead_free(&th);
diff --git a/ext/repo_apkv3.c b/ext/repo_apkv3.c
new file mode 100644 (file)
index 0000000..678b913
--- /dev/null
@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 2024, SUSE LLC
+ *
+ * This program is licensed under the BSD license, read LICENSE.BSD
+ * for further information
+ */
+
+/*
+ * implement processing of uncompressed apkv3 data (without the leading "ADB.")
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "pool.h"
+#include "repo.h"
+#include "util.h"
+#include "chksum.h"
+
+#include "repo_apk.h"
+#include "repo_apkv3.h"
+
+
+#define ADB_MAX_SIZE           0x10000000
+
+static inline unsigned int
+adb_u32(const unsigned char *p)
+{
+  return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+}
+
+static int
+adb_read_blk_header(FILE *fp, unsigned long long *sizep)
+{
+  unsigned char buf[12];
+  unsigned int size;
+  unsigned long long lsize;
+  if (fread(buf, 4, 1, fp) != 1)
+    return -1;
+  size = buf[0] | buf[1] << 8 | buf[2] << 16 | (buf[3] & 0x3f) << 24;
+  if ((buf[3] & 0xc0) != 0xc0)
+    {
+      if (size < 4)
+       return -1;
+      *sizep = size - 4;
+      return (buf[3] & 0xc0) >> 3;
+    }
+  if (fread(buf, 12, 1, fp) != 1)
+    return -1;
+  lsize = adb_u32(buf + 4);
+  lsize |= (unsigned long long)adb_u32(buf + 8) << 32;
+  if (lsize < 16)
+    return -1;
+  *sizep = lsize - 16;
+  return size;
+}
+
+/* low level */
+
+static const unsigned char *
+adb_blob(const unsigned char *adb, size_t adblen, unsigned int v, size_t *bloblp)
+{
+  size_t blobl;
+  int type = (v >> 28) & 15;
+  v &= 0xfffffff;
+  if (type != 8 && type != 9 && type != 10)
+    return 0;
+  if (v + (type == 8 ? 1 : type == 9 ? 2 : 4) > adblen)
+    return 0;
+  blobl = adb[v++];
+  if (type > 8)
+    blobl |= adb[v++] << 8;
+  if (type > 9)
+    {
+      blobl |= adb[v++] << 16;
+      blobl |= adb[v++] << 24;
+    }
+  if (v + blobl > adblen)
+    return 0;
+  *bloblp = blobl;
+  return adb + v;
+}
+
+static int
+adb_num(const unsigned char *adb, size_t adblen, unsigned int v, unsigned long long *nump)
+{
+  unsigned long long num;
+  int type = (v >> 28) & 15;
+  v &= 0xfffffff;
+  if (type != 1 && type != 2 && type != 3)
+    return 0;
+  if (type == 1)
+    {
+      *nump = v;
+      return 1;
+    }
+  if (v + (type == 2 ? 4 : 8) > adblen)
+    return 0;
+  num = adb_u32(adb + v);
+  if (type == 3)
+    num |= (unsigned long long)adb_u32(adb + v + 4) << 32;
+  *nump = num;
+  return 1;
+}
+
+static unsigned int
+adb_arr(const unsigned char *adb, size_t adblen, unsigned int v)
+{
+  unsigned int cnt;
+  int type = (v >> 28) & 15;
+  v &= 0xfffffff;
+  if (type != 13 && type != 14)
+    return 0;
+  if (v + 4 > adblen)
+    return 0;
+  cnt = adb_u32(adb + v);
+  if (cnt == 0 || cnt >= 0x1000000 || v + 4 * cnt > adblen)
+    return 0;
+  return cnt;
+}
+
+static inline unsigned int
+adb_idx(const unsigned char *adb, unsigned int v, unsigned int cnt, unsigned int idx)
+{
+  if (idx >= cnt)
+    return 0;
+  v = (v & 0xfffffff) + idx * 4;
+  return adb_u32(adb + v);
+}
+
+/* high level */
+
+static Id
+adb_poolid(const unsigned char *adb, size_t adblen, unsigned int v, Pool *pool)
+{
+  size_t blobl;
+  const unsigned char *blob = adb_blob(adb, adblen, v, &blobl);
+  return blob && blobl < 0x1000000 ? pool_strn2id(pool, (const char *)blob, (unsigned int)blobl, 1) : 0;
+}
+
+static void
+adb_setstr(const unsigned char *adb, size_t adblen, unsigned int v, Repodata *data, Id p, Id key)
+{
+  size_t blobl;
+  const unsigned char *blob = adb_blob(adb, adblen, v, &blobl);
+  if (blob && blobl < 0x1000000)
+    {
+      char *space = pool_alloctmpspace(data->repo->pool, blobl + 1);
+      memcpy(space, blob, blobl);
+      space[blobl] = 0;
+      repodata_set_str(data, p, key, space);
+      pool_freetmpspace(data->repo->pool, space);
+    }
+}
+
+static void
+adb_setnum(const unsigned char *adb, size_t adblen, unsigned int v, Repodata *data, Id p, Id key)
+{
+  unsigned long long x;
+  if (adb_num(adb, adblen, v, &x))
+    repodata_set_num(data, p, key, x);
+}
+
+static void
+adb_setchksum(const unsigned char *adb, size_t adblen, unsigned int v, Repodata *data, Id p, Id key)
+{
+  size_t blobl;
+  const unsigned char *blob = adb_blob(adb, adblen, v, &blobl);
+  if (blob && blobl == 20)
+    repodata_set_bin_checksum(data, p, key, REPOKEY_TYPE_SHA1, blob);
+  else if (blob && blobl == 32)
+    repodata_set_bin_checksum(data, p, key, REPOKEY_TYPE_SHA256, blob);
+  else if (blob && blobl == 64)
+    repodata_set_bin_checksum(data, p, key, REPOKEY_TYPE_SHA512, blob);
+}
+
+
+static Id
+adb_get_dep(const unsigned char *adb, size_t adblen, unsigned int v, Pool *pool, Id *whatp)
+{
+  unsigned int cnt;
+  Id id, evr;
+  unsigned long long match;;
+  if (!(cnt = adb_arr(adb, adblen, v)))
+    return 0;
+  id = adb_poolid(adb, adblen, adb_idx(adb, v, cnt, 1), pool);
+  if (!id)
+    return 0;
+  evr = cnt > 2 ? adb_poolid(adb, adblen, adb_idx(adb, v, cnt, 2), pool) : 0;
+  match = cnt > 3 ? adb_num(adb, adblen, adb_idx(adb, v, cnt, 3), &match) : 1;
+  if (match & 16)
+    {
+      if (!whatp || *whatp != SOLVABLE_REQUIRES)
+       return 0;
+      *whatp = SOLVABLE_CONFLICTS;
+    }
+  if (evr)
+    {
+      int flags = 0;
+      if (match & 1)
+       flags |= REL_EQ;
+      if (match & 2)
+       flags |= REL_LT;
+      if (match & 4)
+       flags |= REL_GT;
+      if (match & 8)
+       {
+         /* fuzzy match, prepend ~ to evr */
+         char *space = pool_alloctmpspace(pool, strlen(pool_id2str(pool, evr)) + 2);
+         space[0] = '~';
+         strcpy(space, pool_id2str(pool, evr));
+         evr = pool_str2id(pool, space, 1);
+         pool_freetmpspace(pool, space);
+       }
+      id = pool_rel2id(pool, id, evr, flags, 1);
+    }
+  return id;
+}
+
+static void
+adb_add_deps(const unsigned char *adb, size_t adblen, unsigned int v, Repodata *data, Id p, Id what)
+{
+  Id supplements = 0;
+  Id id, oldwhat = what;
+  Repo *repo = data->repo;
+  Pool *pool = repo->pool;
+  Solvable *s = pool->solvables + p;
+  unsigned int cnt, idx;
+  if (!(cnt = adb_arr(adb, adblen, v)))
+    return;
+  for (idx = 1; idx < cnt; idx++)
+    {
+      unsigned int vv = adb_idx(adb, v, cnt, idx);
+      if (!vv)
+       continue;
+      what = oldwhat;
+      id = adb_get_dep(adb, adblen, vv, pool, &what);
+      if (!id)
+       continue;
+      if (what == SOLVABLE_PROVIDES)
+        s->provides = repo_addid_dep(repo, s->provides, id, 0);
+      else if (what == SOLVABLE_REQUIRES)
+        s->requires = repo_addid_dep(repo, s->requires, id, 0);
+      else if (what == SOLVABLE_CONFLICTS)
+        s->conflicts = repo_addid_dep(repo, s->conflicts, id, 0);
+      else if (what == SOLVABLE_RECOMMENDS)
+        s->recommends = repo_addid_dep(repo, s->recommends, id, 0);
+      else if (what == SOLVABLE_SUPPLEMENTS)
+       supplements = supplements ? pool_rel2id(pool, id, supplements, REL_AND, 1) : id;
+    }
+  if (supplements)
+    s->supplements = repo_addid_dep(repo, s->supplements, supplements, 0);
+}
+
+static Id
+adb_add_pkg_info(Pool *pool, Repo *repo, Repodata *data, const unsigned char *adb, size_t adblen, unsigned int v, int flags)
+{
+  Solvable *s;
+  unsigned int cnt;
+  Id name, origin, license;
+  
+  if (!(cnt = adb_arr(adb, adblen, v)))
+    return 0;
+  name = adb_poolid(adb, adblen, adb_idx(adb, v, cnt, 1), pool);
+  if (!name)
+    return 0;
+  s = pool_id2solvable(pool, repo_add_solvable(repo));
+  s->name = name;
+  s->evr = adb_poolid(adb, adblen, adb_idx(adb, v, cnt, 2), pool);
+  adb_setstr(adb, adblen, adb_idx(adb, v, cnt, 4), data, s - pool->solvables, SOLVABLE_DESCRIPTION);
+  s->arch = adb_poolid(adb, adblen, adb_idx(adb, v, cnt, 5), pool);
+  license = adb_poolid(adb, adblen, adb_idx(adb, v, cnt, 6), pool);
+  if (license)
+    repodata_set_id(data, s - pool->solvables, SOLVABLE_LICENSE, license);
+  origin = adb_poolid(adb, adblen, adb_idx(adb, v, cnt, 7), pool);
+  if (origin && origin != s->name)
+    repodata_set_id(data, s - pool->solvables, SOLVABLE_SOURCENAME, origin);
+  else
+    repodata_set_void(data, s - pool->solvables, SOLVABLE_SOURCENAME);
+  adb_setstr(adb, adblen, adb_idx(adb, v, cnt, 9), data, s - pool->solvables, SOLVABLE_URL);
+  adb_setnum(adb, adblen, adb_idx(adb, v, cnt, 11), data, s - pool->solvables, SOLVABLE_BUILDTIME);
+  adb_setnum(adb, adblen, adb_idx(adb, v, cnt, 12), data, s - pool->solvables, SOLVABLE_INSTALLSIZE);
+  adb_setnum(adb, adblen, adb_idx(adb, v, cnt, 13), data, s - pool->solvables, SOLVABLE_DOWNLOADSIZE);
+  if ((flags & APK_ADD_WITH_HDRID) != 0)
+    adb_setchksum(adb, adblen, adb_idx(adb, v, cnt, 3), data, s - pool->solvables, SOLVABLE_HDRID);
+  adb_add_deps(adb, adblen, adb_idx(adb, v, cnt, 15), data, s - pool->solvables, SOLVABLE_REQUIRES);
+  adb_add_deps(adb, adblen, adb_idx(adb, v, cnt, 16), data, s - pool->solvables, SOLVABLE_PROVIDES);
+  adb_add_deps(adb, adblen, adb_idx(adb, v, cnt, 18), data, s - pool->solvables, SOLVABLE_SUPPLEMENTS);
+  adb_add_deps(adb, adblen, adb_idx(adb, v, cnt, 19), data, s - pool->solvables, SOLVABLE_RECOMMENDS);
+  if (!s->arch)
+    s->arch = ARCH_NOARCH;
+  if (!s->evr)
+    s->evr = ID_EMPTY;
+  s->provides = repo_addid_dep(repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0);
+  return s - pool->solvables;
+}
+
+static Id
+add_add_idb_pkg(Pool *pool, Repo *repo, Repodata *data, const unsigned char *adb, size_t adblen, unsigned int v, int flags)
+{
+  unsigned int cnt;
+  size_t blobl;
+  const unsigned char *blob = adb_blob(adb, adblen, v, &blobl);
+  if (blobl < 4 + 2 + 4 || blobl >= ADB_MAX_SIZE || (blob[3] & 0xf0) != 0)
+    return 0;
+  adb = (unsigned char *)blob + 4;
+  adblen = blobl - 4;
+  v = adb_u32(adb + 4);
+  if (!(cnt = adb_arr(adb, adblen, v)))
+    return 0;
+  return adb_add_pkg_info(pool, repo, data, adb, adblen, adb_idx(adb, v, cnt, 1), flags);
+}
+
+static const unsigned char *
+adb_read_adb_blk(Pool *pool, FILE *fp, const char *fn, size_t *adblenp)
+{
+  unsigned char *adb;
+  unsigned long long size;
+  if (adb_read_blk_header(fp, &size) != 0)
+    {
+      pool_error(pool, -1, "%s: missing adb block", fn);
+      return 0;
+    }
+  if (size > ADB_MAX_SIZE)
+    {
+      pool_error(pool, -1, "%s: oversized adb block", fn);
+      return 0;
+    }
+  adb = solv_malloc((size_t)size);
+  if (fread(adb, (size_t)size, 1, fp) != 1)
+    {
+      pool_error(pool, -1, "%s: adb block read error", fn);
+      return 0;
+    }
+  *adblenp = (size_t)size;
+  return adb;
+}
+
+Id
+apkv3_add_pkg(Repo *repo, Repodata *data, const char *fn, FILE *fp, int flags)
+{
+  Pool *pool = repo->pool;
+  char buf[4];
+  const unsigned char *adb;
+  size_t adblen;
+  unsigned int v, cnt;
+  Id p;
+
+  if (fread(buf, 4, 1, fp) != 1 || buf[0] != 'p' || buf[1] != 'c' || buf[2] != 'k' || buf[3] != 'g')
+    {
+      pool_error(pool, -1, "%s: not an apkv3 package", fn);
+      return 0;
+    }
+
+  adb = adb_read_adb_blk(pool, fp, fn, &adblen);
+  if (!adb)
+    return 0;
+
+  v = adb_u32(adb + 4);
+  if (!(cnt = adb_arr(adb, adblen, v)))
+    {
+      solv_free((void *)adb);
+      return 0;
+    }
+  p = adb_add_pkg_info(pool, repo, data, adb, adblen, adb_idx(adb, v, cnt, 1), flags);
+  if (p && (flags & APK_ADD_WITH_PKGID) != 0)
+    {
+      unsigned char pkgid[16];
+      Chksum *pkgidchk = solv_chksum_create(REPOKEY_TYPE_MD5);
+      solv_chksum_add(pkgidchk, adb, adblen);
+      solv_chksum_free(pkgidchk, pkgid);
+      repodata_set_bin_checksum(data, p, SOLVABLE_PKGID, REPOKEY_TYPE_MD5, pkgid);
+    }
+  solv_free((void *)adb);
+  return p;
+}
+
+int
+apkv3_add_idx(Repo *repo, Repodata *data, FILE *fp, int flags)
+{
+  Pool *pool = repo->pool;
+  char buf[4];
+  const unsigned char *adb;
+  size_t adblen;
+  unsigned int v, cnt, idx;
+  int idb = flags & APK_ADD_INDEX ? 1 : 0;
+
+  if (fread(buf, 4, 1, fp) != 1 || memcmp(buf, (idb ? "idb" : "indx") , 4))
+    {
+      pool_error(pool, -1, (idb ?  "not an apkv3 installed database" : "not an apkv3 index"));
+      return -1;
+    }
+
+  adb = adb_read_adb_blk(pool, fp, idb ? "installed database" : "index", &adblen);
+  if (!adb)
+    return -1;
+
+  v = adb_u32(adb + 4);
+  if (!(cnt = adb_arr(adb, adblen, v)))
+    {
+      solv_free((void *)adb);
+      return -1;
+    }
+  v = adb_idx(adb, v, cnt, idb ? 1 : 2);
+  if ((cnt = adb_arr(adb, adblen, v)) != 0)
+    {
+      for (idx = 1; idx < cnt; idx++)
+       {
+         if (idb)
+            add_add_idb_pkg(pool, repo, data, adb, adblen, adb_idx(adb, v, cnt, idx), flags);
+         else
+            adb_add_pkg_info(pool, repo, data, adb, adblen, adb_idx(adb, v, cnt, idx), flags);
+       }
+    }
+  solv_free((void *)adb);
+  return 0;
+}
+
diff --git a/ext/repo_apkv3.h b/ext/repo_apkv3.h
new file mode 100644 (file)
index 0000000..1ec34c0
--- /dev/null
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2025, SUSE LLC
+ *
+ * This program is licensed under the BSD license, read LICENSE.BSD
+ * for further information
+ */
+
+extern Id apkv3_add_pkg(Repo *repo, Repodata *data, const char *fn, FILE *fp, int flags);
+extern int apkv3_add_idx(Repo *repo, Repodata *data, FILE *fp, int flags);
+