]> git.ipfire.org Git - thirdparty/libsolv.git/commitdiff
Add new repo2solv tool replacing the old shell script
authorMichael Schroeder <mls@suse.de>
Mon, 16 Jul 2018 13:56:04 +0000 (15:56 +0200)
committerMichael Schroeder <mls@suse.de>
Mon, 16 Jul 2018 13:56:04 +0000 (15:56 +0200)
tools/CMakeLists.txt
tools/common_write.c
tools/repo2solv.c [new file with mode: 0644]

index 6b592f7f69a776b8ef6e56c67ca3a9be4a45e482..abb9522aee758b2c6a37f88c5de4d9a81e560e1f 100644 (file)
@@ -4,7 +4,7 @@
 
 ADD_LIBRARY (toolstuff STATIC common_write.c)
 
-SET (tools_list mergesolv dumpsolv installcheck testsolv)
+SET (tools_list mergesolv dumpsolv installcheck testsolv repo2solv)
 
 IF (ENABLE_RPMDB)
 ADD_EXECUTABLE (rpmdb2solv rpmdb2solv.c)
@@ -112,6 +112,8 @@ TARGET_LINK_LIBRARIES (mergesolv toolstuff libsolvext libsolv ${SYSTEM_LIBRARIES
 ADD_EXECUTABLE (testsolv testsolv.c)
 TARGET_LINK_LIBRARIES (testsolv libsolvext libsolv ${SYSTEM_LIBRARIES})
 
+ADD_EXECUTABLE (repo2solv repo2solv.c )
+TARGET_LINK_LIBRARIES (repo2solv toolstuff libsolvext libsolv ${SYSTEM_LIBRARIES})
+
 INSTALL (TARGETS ${tools_list} DESTINATION ${BIN_INSTALL_DIR})
 
-INSTALL (PROGRAMS repo2solv.sh DESTINATION ${BIN_INSTALL_DIR})
index e20f64f279f1c5804b03c3fd5d5e1ccfc3678810..577b1aba564154b19506902ba92b6f45c6984d3d 100644 (file)
@@ -65,6 +65,8 @@ keyfilter_solv(Repo *data, Repokey *key, void *kfdata)
     return KEY_STORAGE_DROPPED;
   if (!kd->haveexternal && key->name == REPOSITORY_EXTERNAL)
     return KEY_STORAGE_DROPPED;
+  if (key->name == SUSETAGS_SHARE_NAME || key->name == SUSETAGS_SHARE_EVR || key->name == SUSETAGS_SHARE_ARCH)
+    return KEY_STORAGE_DROPPED;
   for (i = 0; verticals[i]; i++)
     if (key->name == verticals[i])
       return KEY_STORAGE_VERTICAL_OFFSET;
diff --git a/tools/repo2solv.c b/tools/repo2solv.c
new file mode 100644 (file)
index 0000000..c81680c
--- /dev/null
@@ -0,0 +1,825 @@
+/*
+ * Copyright (c) 2018, SUSE LLC.
+ *
+ * This program is licensed under the BSD license, read LICENSE.BSD
+ * for further information
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include "pool.h"
+#include "repo.h"
+
+#ifdef ENABLE_RPMPKG
+#include "repo_rpmdb.h"
+#endif
+
+#ifdef ENABLE_RPMMD
+#include "repo_repomdxml.h"
+#include "repo_rpmmd.h"
+#include "repo_updateinfoxml.h"
+#include "repo_deltainfoxml.h"
+#endif
+
+#ifdef ENABLE_SUSEREPO
+#include "repo_content.h"
+#include "repo_susetags.h"
+#endif
+
+#ifdef SUSE
+#include "repo_autopattern.h"
+#endif
+#ifdef ENABLE_APPDATA
+#include "repo_appdata.h"
+#endif
+#include "common_write.h"
+#include "solv_xfopen.h"
+
+
+#ifdef SUSE
+int add_auto = 0;
+#endif
+#ifdef ENABLE_APPDATA
+int add_appdata = 0;
+#endif
+int recursive = 0;
+int add_filelist = 0;
+int add_changelog = 0;
+int filtered_filelist = 0;
+
+
+#define REPO_PLAINDIR          1
+#define REPO_RPMMD             2
+#define REPO_RPMMD_REPODATA    3
+#define REPO_SUSETAGS          4
+
+int
+autodetect_repotype(Pool *pool, const char *dir)
+{
+  struct stat stb;
+  char *tmp;
+  FILE *fp;
+
+  tmp = pool_tmpjoin(pool, dir, "/repomd.xml", 0);
+  if (stat(tmp, &stb) == 0)
+    return REPO_RPMMD;
+  tmp = pool_tmpjoin(pool, dir, "/repodata/repomd.xml", 0);
+  if (stat(tmp, &stb) == 0)
+    return REPO_RPMMD_REPODATA;
+  tmp = pool_tmpjoin(pool, dir, "/content", 0);
+  if ((fp = fopen(tmp, "r")) != 0)
+    {
+      char buf[512], *descrdir = 0;
+      while (fgets(buf, sizeof(buf), fp))
+       {
+         int l = strlen(buf);
+         char *bp = buf;
+         if (buf[l - 1] != '\n')
+           {
+             int c;
+             while ((c = getc(fp)) != EOF && c != '\n')
+               ;
+             continue;
+           }
+         while (l && (buf[l - 1] == '\n' || buf[l - 1] == ' ' || buf[l - 1] == '\t'))
+           l--;
+         buf[l] = 0;
+         while (*bp == ' ' || *bp == '\t')
+           bp++;
+         if (strncmp(bp, "DESCRDIR", 8) != 0 || (bp[8] != ' ' && bp[8] != '\t'))
+           continue;
+         bp += 9;
+         while (*bp == ' ' || *bp == '\t')
+           bp++;
+         descrdir = bp;
+         break;
+       }
+      fclose(fp);
+      if (descrdir)
+       {
+         tmp = pool_tmpjoin(pool, dir, "/", descrdir);
+         if (stat(tmp, &stb) == 0 && S_ISDIR(stb.st_mode))
+           return REPO_SUSETAGS;
+       }
+    }
+  tmp = pool_tmpjoin(pool, dir, "/suse/setup/descr", 0);
+  if (stat(tmp, &stb) == 0 && S_ISDIR(stb.st_mode))
+    return REPO_SUSETAGS;
+  return REPO_PLAINDIR;
+}
+
+
+#ifdef ENABLE_RPMPKG
+
+int
+read_plaindir_repo(Repo *repo, const char *dir)
+{
+  Pool *pool = repo->pool;
+  Repodata *data;
+  int c;
+  FILE *fp;
+  int wstatus;
+  int fds[2];
+  pid_t pid;
+  char *buf = 0;
+  char *buf_end = 0;
+  char *bp = 0;
+  char *rpm;
+  int res = 0;
+  Id p;
+
+  /* run find command */
+  if (pipe(fds))
+    {
+      perror("pipe");
+      exit(1);
+    }
+  while ((pid = fork()) == (pid_t)-1)
+    {
+      if (errno != EAGAIN)
+       {
+         perror("fork");
+         exit(1);
+       }
+      sleep(3);
+    }
+  if (pid == 0)
+    {
+      if (chdir(dir))
+       {
+         perror(dir);
+         _exit(1);
+       }
+      close(fds[0]);
+      if (fds[1] != 1)
+       {
+         if (dup2(fds[1], 1) == -1)
+           {
+             perror("dup2");
+             _exit(1);
+           }
+         close(fds[1]);
+       }
+      if (recursive)
+       execl("/usr/bin/find", ".", "-name", ".", "-o", "-name", ".*", "-prune", "-o", "-name", "*.delta.rpm", "-o", "-name", "*.patch.rpm", "-o", "-name", "*.rpm", "-a", "-type", "f", "-print0", (char *)0);
+      else
+       execl("/usr/bin/find", ".", "-maxdepth", "1", "-name", ".", "-o", "-name", ".*", "-prune", "-o", "-name", "*.delta.rpm", "-o", "-name", "*.patch.rpm", "-o", "-name", "*.rpm", "-a", "-type", "f", "-print0", (char *)0);
+      perror("/usr/bin/find");
+      _exit(1);
+    }
+  close(fds[1]);
+  if ((fp = fdopen(fds[0], "r")) == 0)
+    {
+      perror("fdopen");
+      exit(1);
+    }
+  data = repo_add_repodata(repo, 0);
+  bp = buf;
+  while ((c = getc(fp)) != EOF)
+    {
+      if (bp == buf_end)
+       {
+         size_t len = bp - buf;
+         buf = solv_realloc(buf, len + 4096);
+         bp = buf + len;
+         buf_end = bp + 4096;
+       }
+      *bp++ = c;
+      if (c)
+       continue;
+      bp = buf;
+      rpm = solv_dupjoin(dir, "/", bp[0] == '.' && bp[1] == '/' ? bp + 2 : bp);
+      if ((p = repo_add_rpm(repo, rpm, REPO_REUSE_REPODATA|REPO_NO_INTERNALIZE|REPO_NO_LOCATION|(filtered_filelist ? RPM_ADD_FILTERED_FILELIST : 0))) == 0)
+       {
+         fprintf(stderr, "%s: %s\n", rpm, pool_errstr(pool));
+#if 0
+         res = 1;
+#endif
+       }
+      else
+       repodata_set_location(data, p, 0, 0, bp[0] == '.' && bp[1] == '/' ? bp + 2 : bp);
+      solv_free(rpm);
+    }
+  fclose(fp);
+  while (waitpid(pid, &wstatus, 0) == -1)
+    {
+      if (errno == EINTR)
+       continue;
+      perror("waitpid");
+      exit(1);
+    }
+  if (wstatus)
+    {
+      fprintf(stderr, "find: exit status %d\n", (wstatus >> 8) | (wstatus & 255) << 8);
+#if 0
+      res = 1;
+#endif
+    }
+  repo_internalize(repo);
+  return res;
+}
+
+#else
+
+int
+read_plaindir_repo(Repo *repo, const char *dir)
+{
+  fprintf(stderr, "plaindir repo type is not supported\n");
+  exit(1);
+}
+
+#endif
+
+#ifdef ENABLE_SUSEREPO
+
+static const char *
+susetags_find(char **files, int nfiles, const char *what)
+{
+  int i;
+  size_t l = strlen(what);
+  for (i = 0; i < nfiles; i++)
+    {
+      char *fn = files[i];
+      if (strncmp(fn, what, l) != 0)
+       continue;
+      if (fn[l] == 0)
+       return fn;
+      if (fn[l] != '.')
+       continue;
+      if (strchr(fn + l + 1, '.') != 0)
+       continue;
+      if (solv_xfopen_iscompressed(fn) <= 0)
+       continue;
+      return fn;
+    }
+  return 0;
+}
+
+static FILE *
+susetags_open(const char *dir, const char *filename, char **tmpp, int missingok)
+{
+  FILE *fp;
+  if (!filename)
+    {
+      *tmpp = 0;
+      return 0;
+    }
+  *tmpp = solv_dupjoin(dir, "/", filename);
+  if ((fp = solv_xfopen(*tmpp, "r")) == 0)
+    {
+      if (!missingok)
+       {
+         perror(*tmpp);
+         exit(1);
+       }
+      *tmpp = solv_free(*tmpp);
+      return 0;
+    }
+  return fp;
+}
+
+static void
+susetags_extend(Repo *repo, const char *dir, char **files, int nfiles, char *what, Id defvendor, char *language, int missingok)
+{
+  const char *filename;
+  FILE *fp;
+  char *tmp;
+
+  filename = susetags_find(files, nfiles, what);
+  if (!filename)
+    return;
+  if ((fp = susetags_open(dir, filename, &tmp, missingok)) != 0)
+    {
+      if (repo_add_susetags(repo, fp, defvendor, language, REPO_EXTEND_SOLVABLES))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(repo->pool));
+         exit(1);
+       }
+      fclose(fp);
+      solv_free(tmp);
+    }
+}
+
+static void
+susetags_extend_languages(Repo *repo, const char *dir, char **files, int nfiles, Id defvendor, int missingok)
+{
+  int i;
+  for (i = 0; i < nfiles; i++)
+    {
+      char *fn = files[i];
+      char lang[64], *p;
+      if (strncmp(fn, "packages.", 9) != 0)
+       continue;
+      if (strlen(fn + 9) + 1 >= sizeof(lang))
+       continue;
+      strncpy(lang, fn + 9, sizeof(lang) - 1);
+      lang[sizeof(lang) - 1] = 0;
+      p = strrchr(lang, '.');
+      if (p)
+       {
+          if (solv_xfopen_iscompressed(lang) <= 0)
+           continue;
+         *p = 0;
+       }
+      if (strchr(lang, '.'))
+       continue;
+      if (!strcmp(lang, "en"))
+       continue;       /* already did that one */
+      if (!strcmp(lang, "DU"))
+       continue;       /* disk usage */
+      if (!strcmp(lang, "FL"))
+       continue;       /* file list */
+      if (!strcmp(lang, "DL"))
+       continue;       /* deltas */
+      susetags_extend(repo, dir, files, nfiles, fn, defvendor, lang, missingok);
+    }
+}
+
+static int
+susetags_dircmp(const void *ap, const void *bp, void *dp)
+{
+  return strcmp(*(const char **)ap, *(const char **)bp);
+}
+
+int
+read_susetags_repo(Repo *repo, const char *dir)
+{
+  Pool *pool = repo->pool;
+  const char *filename;
+  char *ddir;
+  char *tmp;
+  FILE *fp;
+  Id defvendor = 0;
+  const char *descrdir = "suse/setup/descr";
+  char **files = 0;
+  int nfiles = 0;
+  DIR *dp;
+  struct dirent *de;
+
+  /* read content file */
+  tmp = pool_tmpjoin(pool, dir, "content", 0);
+  repo_add_repodata(repo, 0);
+  if ((fp = fopen(tmp, "r")) != 0)
+    {
+      if (repo_add_content(repo, fp, REPO_REUSE_REPODATA))
+        {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+         exit(1);
+        }
+      fclose(fp);
+      descrdir = repo_lookup_str(repo, SOLVID_META, SUSETAGS_DESCRDIR);
+      defvendor = repo_lookup_id(repo, SOLVID_META, SUSETAGS_DEFAULTVENDOR);
+    }
+
+  /* get content of descrdir directory */
+  ddir = solv_dupjoin(dir, "/", descrdir);
+  if ((dp = opendir(ddir)) == 0)
+    {
+      perror(ddir);
+      exit(1);
+    }
+  while ((de = readdir(dp)) != 0)
+    {
+      if (de->d_name[0] == 0 || de->d_name[0] == '.')
+       continue;
+      files = solv_extend(files, nfiles, 1, sizeof(char *), 63);
+      files[nfiles++] = solv_strdup(de->d_name);
+    }
+  closedir(dp);
+  if (nfiles > 1)
+    solv_sort(files, nfiles, sizeof(char *), susetags_dircmp, 0);
+  
+  /* add packages */
+  filename = susetags_find(files, nfiles, "packages");
+  if (filename && (fp = susetags_open(ddir, filename, &tmp, 1)) != 0)
+    {
+      if (repo_add_susetags(repo, fp, defvendor, 0, REPO_NO_INTERNALIZE|SUSETAGS_RECORD_SHARES))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+         exit(1);
+       }
+      fclose(fp);
+      tmp = solv_free(tmp);
+
+      /* now extend the packages */
+      susetags_extend(repo, ddir, files, nfiles, "packages.DU", defvendor, 0, 1);
+      susetags_extend(repo, ddir, files, nfiles, "packages.en", defvendor, 0, 1);
+      susetags_extend_languages(repo, ddir, files, nfiles, defvendor, 1);
+      if (add_filelist)
+        susetags_extend(repo, ddir, files, nfiles, "packages.FL", defvendor, 0, 1);
+    }
+
+  /* add deltas */
+  filename = susetags_find(files, nfiles, "packages.DL");
+  if (filename && (fp = susetags_open(ddir, filename, &tmp, 1)) != 0)
+    {
+      if (repo_add_susetags(repo, fp, defvendor, 0, 0))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+         exit(1);
+       }
+      fclose(fp);
+      tmp = solv_free(tmp);
+    }
+
+  /* add legacy patterns */
+  tmp = solv_dupjoin(ddir, "/patterns", 0);
+  if ((fp = fopen(tmp, "r")) != 0)
+    {
+      char pbuf[4096];
+
+      repo_add_repodata(repo, 0);
+      while (fgets(pbuf, sizeof(pbuf), fp))
+       {
+         char *p;
+         FILE *pfp;
+         if (strchr(pbuf, '/') != 0)
+           continue;
+         if ((p = strchr(pbuf, '\n')) != 0)
+           *p = 0;
+         if (*pbuf == 0)
+           continue;
+         solv_free(tmp);
+         tmp = solv_dupjoin(ddir, "/", pbuf);
+         if ((pfp = solv_xfopen(tmp, "r")) != 0)
+           {
+             if (repo_add_susetags(repo, pfp, defvendor, 0, REPO_NO_INTERNALIZE|REPO_REUSE_REPODATA))
+               {
+                 fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+                 exit(1);
+               }
+             fclose(pfp);
+           }
+       }
+      fclose(fp);
+    }
+  tmp = solv_free(tmp);
+#ifdef ENABLE_APPDATA
+  /* appdata */
+  filename = add_appdata ? susetags_find(files, nfiles, "appdata.xml") : 0;
+  if (filename && (fp = susetags_open(ddir, filename, &tmp, 1)) != 0)
+    {
+      if (repo_add_appdata(repo, fp, 0))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+         exit(1);
+       }
+      fclose(fp);
+      tmp = solv_free(tmp);
+    }
+#endif
+
+  while (nfiles > 0)
+    solv_free(files[--nfiles]);
+  solv_free(files);
+  solv_free(ddir);
+  repo_internalize(repo);
+  return 0;
+}
+
+#else
+
+int
+read_susetags_repo(Repo *repo, const char *dir)
+{
+  fprintf(stderr, "susetags repo type is not supported\n");
+  exit(1);
+}
+
+#endif
+
+
+#ifdef ENABLE_RPMMD
+
+static const char *
+repomd_find(Repo *repo, const char *what)
+{
+  Pool *pool = repo->pool;
+  Dataiterator di;
+  const char *filename;
+
+  filename = 0;
+  dataiterator_init(&di, pool, repo, SOLVID_META, REPOSITORY_REPOMD_TYPE, what, SEARCH_STRING);
+  dataiterator_prepend_keyname(&di, REPOSITORY_REPOMD);
+  if (dataiterator_step(&di))
+    {
+      dataiterator_setpos_parent(&di);
+      filename = pool_lookup_str(pool, SOLVID_POS, REPOSITORY_REPOMD_LOCATION);
+    }
+  dataiterator_free(&di);
+  if (filename && strncmp(filename, "repodata/", 9) == 0)
+    filename += 9;
+  return filename;
+}
+
+static FILE *
+repomd_open(const char *dir, const char *filename, char **tmpp, int missingok)
+{
+  FILE *fp;
+  if (!filename)
+    {
+      *tmpp = 0;
+      return 0;
+    }
+  *tmpp = solv_dupjoin(dir, "/", filename);
+  if ((fp = solv_xfopen(*tmpp, "r")) == 0)
+    {
+      if (!missingok)
+       {
+         perror(*tmpp);
+         exit(1);
+       }
+      *tmpp = solv_free(*tmpp);
+      return 0;
+    }
+  return fp;
+}
+
+static void
+repomd_extend(Repo *repo, const char *dir, const char *what, const char *language, int missingok)
+{
+  const char *filename;
+  FILE *fp;
+  char *tmp;
+
+  filename = repomd_find(repo, what);
+  if (!filename)
+    return;
+  fp = repomd_open(dir, filename, &tmp, missingok);
+  if (fp)
+    {
+      if (repo_add_rpmmd(repo, fp, language, REPO_EXTEND_SOLVABLES))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(repo->pool));
+         exit(1);
+       }
+      fclose(fp);
+    }
+  solv_free(tmp);
+}
+
+static void
+repomd_extend_languages(Repo *repo, const char *dir, int missingok)
+{
+  Dataiterator di;
+  dataiterator_init(&di, repo->pool, repo, SOLVID_META, REPOSITORY_REPOMD_TYPE, "susedata.", SEARCH_STRINGSTART);
+  dataiterator_prepend_keyname(&di, REPOSITORY_REPOMD);
+  while (dataiterator_step(&di))
+    {
+      char *what = solv_strdup(di.kv.str);
+      repomd_extend(repo, dir, what, what + 9, missingok);
+    }
+  dataiterator_free(&di);
+}
+
+static void
+add_rpmmd_file(Repo *repo, const char *dir, const char *filename, int missingok)
+{
+  FILE *fp;
+  char *tmp;
+
+  fp = repomd_open(dir, filename, &tmp, missingok);
+  if (!fp)
+    return;
+  if (repo_add_rpmmd(repo, fp, 0, 0))
+    {
+      fprintf(stderr, "%s: %s\n", tmp, pool_errstr(repo->pool));
+      exit(1);
+    }
+  fclose(fp);
+  solv_free(tmp);
+}
+
+int
+read_rpmmd_repo(Repo *repo, const char *dir)
+{
+  Pool *pool = repo->pool;
+  FILE *fp;
+  char *tmp = 0;
+  const char *filename;
+
+  /* add repomd.xml and suseinfo.xml */
+  fp = repomd_open(dir, "repomd.xml", &tmp, 0);
+  if (repo_add_repomdxml(repo, fp, 0))
+    {
+      fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+      exit(1);
+    }
+  fclose(fp);
+  tmp = solv_free(tmp);
+  filename = repomd_find(repo, "suseinfo");
+  if (filename && (fp = repomd_open(dir, filename, &tmp, 0)) != 0)
+    {
+      if (repo_add_repomdxml(repo, fp, REPO_REUSE_REPODATA))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+         exit(1);
+       }
+      fclose(fp);
+      tmp = solv_free(tmp);
+    }
+  
+  /* first all primary packages */
+  filename = repomd_find(repo, "primary");
+  if (filename)
+    {
+      add_rpmmd_file(repo, dir, filename, 0);
+      repomd_extend(repo, dir, "susedata", 0, 1);
+      repomd_extend_languages(repo, dir, 1);
+      if (add_filelist)
+        repomd_extend(repo, dir, "filelists", 0, 1);
+      if (add_changelog)
+        repomd_extend(repo, dir, "other", 0, 1);
+    }
+
+  /* some legacy stuff */
+  filename = repomd_find(repo, "products");
+  if (!filename)
+    filename = repomd_find(repo, "product");
+  if (filename)
+    add_rpmmd_file(repo, dir, filename, 1);
+  filename = repomd_find(repo, "patterns");
+    add_rpmmd_file(repo, dir, filename, 1);
+  
+  /* updateinfo */
+  filename = repomd_find(repo, "updateinfo");
+  if (filename && (fp = repomd_open(dir, filename, &tmp, 0)) != 0)
+    {
+      if (repo_add_updateinfoxml(repo, fp, 0))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+         exit(1);
+       }
+      fclose(fp);
+      tmp = solv_free(tmp);
+    }
+
+  /* deltainfo */
+  filename = repomd_find(repo, "deltainfo");
+  if (!filename)
+    filename = repomd_find(repo, "prestodelta");
+  if (filename && (fp = repomd_open(dir, filename, &tmp, 1)) != 0)
+    {
+      if (repo_add_deltainfoxml(repo, fp, 0))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+         exit(1);
+       }
+      fclose(fp);
+      tmp = solv_free(tmp);
+    }
+
+#ifdef ENABLE_APPDATA
+  /* appdata */
+  filename = add_appdata ? repomd_find(repo, "appdata") : 0;
+  if (filename && (fp = repomd_open(dir, filename, &tmp, 1)) != 0)
+    {
+      if (repo_add_appdata(repo, fp, 0))
+       {
+         fprintf(stderr, "%s: %s\n", tmp, pool_errstr(pool));
+         exit(1);
+       }
+      fclose(fp);
+      tmp = solv_free(tmp);
+    }
+#endif
+
+  repo_internalize(repo);
+  return 0;
+}
+
+#else
+
+int
+read_rpmmd_repo(Repo *repo, const char *dir)
+{
+  fprintf(stderr, "rpmmd repo type is not supported\n");
+  exit(1);
+}
+
+#endif
+
+static void
+usage(int status)
+{
+  fprintf(stderr, "\nUsage:\n"
+          "repo2solv [-R] [-X] [-A] [-o <out.solv>] <dir>\n"
+         "  Convert a repository in <dir> to a solv file\n"
+         "  -h : print help & exit\n"
+         "  -o <out.solv>: write to this file instead of stdout\n"
+         "  -F : add filelist\n"
+         "  -R : also search subdirectories for rpms\n"
+         "  -X : generate pattern/product pseudo packages\n"
+         "  -A : add appdata packages\n"
+        );
+   exit(status);
+}
+
+int
+main(int argc, char **argv)
+{
+  int c, res;
+  int repotype = 0;
+  char *outfile = 0;
+  char *dir;
+  struct stat stb;
+  
+  Pool *pool = pool_create();
+  Repo *repo = repo_create(pool, "<repo>");
+
+  while ((c = getopt(argc, argv, "hAXRFo:")) >= 0)
+    {
+      switch(c)
+       {
+        case 'h':
+          usage(0);
+          break;
+       case 'X':
+#ifdef SUSE
+         add_auto = 1;
+#endif
+         break;
+       case 'A':
+#ifdef ENABLE_APPDATA
+         add_appdata = 1;
+#endif
+         break;
+       case 'R':
+         repotype = REPO_PLAINDIR;
+         recursive = 1;
+         break;
+       case 'F':
+         add_filelist = 1;
+         break;
+       case 'o':
+         outfile = optarg;
+         break;
+        default:
+          usage(1);
+          break;
+       }
+    }
+  if (optind + 1 != argc)
+    usage(1);
+  dir = argv[optind];
+  if (stat(dir, &stb))
+    {
+      perror(dir);
+      exit(1);
+    }
+  if (!S_ISDIR(stb.st_mode))
+    {
+      fprintf(stderr, "%s: not a directory\n", dir);
+      exit(1);
+    }
+  dir = solv_strdup(dir);
+  if (repotype == 0)
+    repotype = autodetect_repotype(pool, dir);
+
+  switch (repotype)
+    {
+    case REPO_RPMMD:
+      res = read_rpmmd_repo(repo, dir);
+      break;
+    case REPO_RPMMD_REPODATA:
+      dir = solv_dupappend(dir, "/repodata", 0);
+      res = read_rpmmd_repo(repo, dir);
+      break;
+    case REPO_SUSETAGS:
+      res = read_susetags_repo(repo, dir);
+      break;
+    case REPO_PLAINDIR:
+      res = read_plaindir_repo(repo, dir);
+      break;
+    default:
+      fprintf(stderr, "unknown repotype %d\n", repotype);
+      exit(1);
+    }
+  if (outfile && freopen(outfile, "w", stdout) == 0)
+    {
+      perror(outfile);
+      exit(1);
+    }
+#ifdef SUSE
+  if (add_auto)
+    repo_add_autopattern(repo, 0);
+#endif
+  tool_write(repo, 0, 0);
+  if (fflush(stdout))
+    {
+      perror("fflush");
+      exit(1);
+    }
+  pool_free(pool);
+  solv_free(dir);
+  exit(res);
+}