From 66ddaae25a1a88bb552ca3465af8ae0cd9afb288 Mon Sep 17 00:00:00 2001 From: Michael Schroeder Date: Mon, 16 Jul 2018 15:56:04 +0200 Subject: [PATCH] Add new repo2solv tool replacing the old shell script --- tools/CMakeLists.txt | 6 +- tools/common_write.c | 2 + tools/repo2solv.c | 825 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 831 insertions(+), 2 deletions(-) create mode 100644 tools/repo2solv.c diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 6b592f7f..abb9522a 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -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}) diff --git a/tools/common_write.c b/tools/common_write.c index e20f64f2..577b1aba 100644 --- a/tools/common_write.c +++ b/tools/common_write.c @@ -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 index 00000000..c81680c7 --- /dev/null +++ b/tools/repo2solv.c @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2018, SUSE LLC. + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 ] \n" + " Convert a repository in to a solv file\n" + " -h : print help & exit\n" + " -o : 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, ""); + + 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); +} -- 2.47.2