/* * 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", "/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", "/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); } solv_free(buf); 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 = 0; char **files = 0; int nfiles = 0; DIR *dp; struct dirent *de; /* read content file */ repo_add_repodata(repo, 0); tmp = solv_dupjoin(dir, "/content", 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); } if (!descrdir) descrdir = "suse/setup/descr"; tmp = solv_free(tmp); /* 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, 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 # ifdef ENABLE_ZCHUNK_COMPRESSION static int repomd_exists(const char *dir, const char *filename) { char *path; struct stat stb; int r; if (!filename) return 0; path = solv_dupjoin(dir, "/", filename); r = stat(path, &stb) == 0; solv_free(path); return r; } # endif static const char * repomd_find(Repo *repo, const char *dir, const char *what, int findzchunk) { Pool *pool = repo->pool; Dataiterator di; const char *filename; # ifdef ENABLE_ZCHUNK_COMPRESSION if (findzchunk) { char *what_zck = solv_dupjoin(what, "_zck", 0); filename = repomd_find(repo, dir, what_zck, 0); solv_free(what_zck); if (filename && repomd_exists(dir, filename)) return filename; } # endif 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, dir, what, 1); 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) { char **susedatas = 0; int nsusedatas = 0, i; 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 *str = solv_strdup(di.kv.str); size_t l = strlen(str); if (l > 4 && !strcmp(str + l - 4, "_zck")) str[l - 4] = 0; for (i = 0; i < nsusedatas; i++) if (!strcmp(susedatas[i], str)) break; if (i < nsusedatas) { solv_free(str); continue; /* already have that entry */ } susedatas = solv_extend(susedatas, nsusedatas, 1, sizeof(char *), 15); susedatas[nsusedatas++] = str; } dataiterator_free(&di); for (i = 0; i < nsusedatas; i++) { repomd_extend(repo, dir, susedatas[i], susedatas[i] + 9, missingok); susedatas[i] = solv_free(susedatas[i]); } solv_free(susedatas); } 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, dir, "suseinfo", 0); 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, dir, "primary", 1); 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, dir, "products", 0); if (!filename) filename = repomd_find(repo, dir, "product", 0); if (filename) add_rpmmd_file(repo, dir, filename, 1); filename = repomd_find(repo, dir, "patterns", 0); add_rpmmd_file(repo, dir, filename, 1); /* updateinfo */ filename = repomd_find(repo, dir, "updateinfo", 1); 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, dir, "deltainfo", 1); if (!filename) filename = repomd_find(repo, dir, "prestodelta", 1); 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, dir, "appdata", 1) : 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, "hAXRFCo:")) >= 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 'C': add_changelog = 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); repo_mark_retracted_packages(repo, pool_str2id(pool, "retracted-patch-package()", 1)); #endif tool_write(repo, stdout); pool_free(pool); solv_free(dir); exit(res); }