/* * Copyright (c) 2009, Novell Inc. * * This program is licensed under the BSD license, read LICENSE.BSD * for further information */ #include #include #include #include #include #include #include #ifdef ENABLE_ZLIB_COMPRESSION #include #endif #ifdef ENABLE_LZMA_COMPRESSION #include #endif #ifdef ENABLE_ZSTD_COMPRESSION #include #endif #include "pool.h" #include "repo.h" #include "util.h" #include "solver.h" /* for GET_USERINSTALLED_ flags */ #include "chksum.h" #include "repo_deb.h" #ifdef _WIN32 #include "strfncs.h" #endif #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif #define MAX_CONTROL_SIZE 0x1000000 #ifdef ENABLE_ZLIB_COMPRESSION static unsigned char * decompress_gz(unsigned char *in, int inl, int *outlp, int maxoutl) { z_stream strm; int outl, ret; unsigned char *bp, *out; /* first skip the gz header */ if (inl <= 10 || in[0] != 0x1f || in[1] != 0x8b) return 0; if (in[2] != 8 || (in[3] & 0xe0) != 0) return 0; bp = in + 4; bp += 6; /* skip time, xflags and OS code */ if (in[3] & 0x04) { /* skip extra field */ int l = bp + 2 >= in + inl ? 0 : (bp[0] | bp[1] << 8); bp += l + 2; } if (in[3] & 0x08) /* orig filename */ while (bp < in + inl && *bp++) ; if (in[3] & 0x10) /* file comment */ while (bp < in + inl && *bp++) ; if (in[3] & 0x02) /* header crc */ bp += 2; if (bp >= in + inl) return 0; inl -= bp - in; in = bp; memset(&strm, 0, sizeof(strm)); strm.next_in = in; strm.avail_in = inl; out = solv_malloc(4096); strm.next_out = out; strm.avail_out = 4096; outl = 0; ret = inflateInit2(&strm, -MAX_WBITS); if (ret != Z_OK) { free(out); return 0; } for (;;) { if (strm.avail_out == 0) { outl += 4096; if (outl >= maxoutl) { inflateEnd(&strm); free(out); return 0; } out = solv_realloc(out, outl + 4096); strm.next_out = out + outl; strm.avail_out = 4096; } ret = inflate(&strm, Z_NO_FLUSH); if (ret == Z_STREAM_END) break; if (ret != Z_OK) { inflateEnd(&strm); free(out); return 0; } } outl += 4096 - strm.avail_out; inflateEnd(&strm); *outlp = outl; return out; } #else static unsigned char * decompress_gz(unsigned char *in, int inl, int *outlp, int maxoutl) { return 0; } #endif /* ENABLE_ZLIB_COMPRESSION */ #ifdef ENABLE_LZMA_COMPRESSION static unsigned char * decompress_xz(unsigned char *in, int inl, int *outlp, int maxoutl) { static lzma_stream stream_init = LZMA_STREAM_INIT; lzma_stream strm; int outl, ret; unsigned char *out; strm = stream_init; strm.next_in = in; strm.avail_in = inl; out = solv_malloc(4096); strm.next_out = out; strm.avail_out = 4096; outl = 0; ret = lzma_auto_decoder(&strm, 100 << 20, 0); if (ret != LZMA_OK) { free(out); return 0; } for (;;) { if (strm.avail_out == 0) { outl += 4096; if (outl >= maxoutl) { lzma_end(&strm); free(out); return 0; } out = solv_realloc(out, outl + 4096); strm.next_out = out + outl; strm.avail_out = 4096; } ret = lzma_code(&strm, LZMA_RUN); if (ret == LZMA_STREAM_END) break; if (ret != LZMA_OK) { lzma_end(&strm); free(out); return 0; } } outl += 4096 - strm.avail_out; lzma_end(&strm); *outlp = outl; return out; } #else static unsigned char * decompress_xz(unsigned char *in, int inl, int *outlp, int maxoutl) { return 0; } #endif /* ENABLE_LZMA_COMPRESSION */ #ifdef ENABLE_ZSTD_COMPRESSION static unsigned char * decompress_zstd(unsigned char *in, int inl, int *outlp, int maxoutl) { ZSTD_DStream *dstream; ZSTD_inBuffer inbuf; ZSTD_outBuffer outbuf; int ret; dstream = ZSTD_createDStream(); if (!dstream) return 0; if (ZSTD_isError(ZSTD_initDStream(dstream))) { ZSTD_freeDStream(dstream); return 0; } inbuf.src = in; inbuf.pos = 0; inbuf.size = inl; outbuf.dst = solv_malloc(4096); outbuf.pos = 0; outbuf.size = 4096; for (;;) { if (outbuf.pos == outbuf.size) { outbuf.size += 4096; if (outbuf.size >= maxoutl) { ret = 1; break; } outbuf.dst = solv_realloc(outbuf.dst, outbuf.size + 4096); } ret = ZSTD_decompressStream(dstream, &outbuf, &inbuf); if (ret == 0 && inbuf.pos == inbuf.size) break; if (ZSTD_isError(ret) || (inbuf.pos == inbuf.size && outbuf.pos < outbuf.size)) { ret = 1; break; } } ZSTD_freeDStream(dstream); if (ret) { solv_free(outbuf.dst); return 0; } *outlp = outbuf.pos; return outbuf.dst; } #else static unsigned char * decompress_zstd(unsigned char *in, int inl, int *outlp, int maxoutl) { return 0; } #endif /* ENABLE_ZSTD_COMPRESSION */ static Id parseonedep(Pool *pool, char *p) { char *n, *ne, *e, *ee; Id name, evr; int flags; while (*p == ' ' || *p == '\t' || *p == '\n') p++; if (!*p || *p == '(') return 0; n = p; /* find end of name */ while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '(' && *p != '|') p++; ne = p; while (*p == ' ' || *p == '\t' || *p == '\n') p++; evr = 0; flags = 0; e = ee = 0; if (*p == '(') { p++; while (*p == ' ' || *p == '\t' || *p == '\n') p++; if (*p == '>') flags |= REL_GT; else if (*p == '=') flags |= REL_EQ; else if (*p == '<') flags |= REL_LT; if (flags) { p++; if (*p == '>') flags |= REL_GT; else if (*p == '=') flags |= REL_EQ; else if (*p == '<') flags |= REL_LT; else p--; p++; } while (*p == ' ' || *p == '\t' || *p == '\n') p++; e = p; while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != ')') p++; ee = p; while (*p && *p != ')') p++; if (*p) p++; while (*p == ' ' || *p == '\t' || *p == '\n') p++; } if (ne - n > 4 && ne[-4] == ':' && !strncmp(ne - 4, ":any", 4)) { /* multiarch annotation */ name = pool_strn2id(pool, n, ne - n - 4, 1); name = pool_rel2id(pool, name, ARCH_ANY, REL_MULTIARCH, 1); } else name = pool_strn2id(pool, n, ne - n, 1); if (e) { evr = pool_strn2id(pool, e, ee - e, 1); name = pool_rel2id(pool, name, evr, flags, 1); } if (*p == '|') { Id id = parseonedep(pool, p + 1); if (id) name = pool_rel2id(pool, name, id, REL_OR, 1); } return name; } static unsigned int makedeps(Repo *repo, char *deps, unsigned int olddeps, Id marker) { Pool *pool = repo->pool; char *p; Id id; while ((p = strchr(deps, ',')) != 0) { *p = 0; olddeps = makedeps(repo, deps, olddeps, marker); *p = ','; deps = p + 1; } id = parseonedep(pool, deps); if (!id) return olddeps; return repo_addid_dep(repo, olddeps, id, marker); } /* put data from control file into the solvable */ /* warning: does inplace changes */ static void control2solvable(Solvable *s, Repodata *data, char *control) { Repo *repo = s->repo; Pool *pool = repo->pool; char *p, *q, *end, *tag; int x, l; int havesource = 0; char checksum[32 * 2 + 1]; Id checksumtype = 0; Id newtype; p = control; while (*p) { p = strchr(p, '\n'); if (!p) break; if (p[1] == ' ' || p[1] == '\t') { char *q; /* continuation line */ q = p - 1; while (q >= control && *q == ' ' && *q == '\t') q--; l = q + 1 - control; if (l) memmove(p + 1 - l, control, l); control = p + 1 - l; p[1] = '\n'; p += 2; continue; } end = p - 1; if (*p) *p++ = 0; /* strip trailing space */ while (end >= control && (*end == ' ' || *end == '\t')) *end-- = 0; tag = control; control = p; q = strchr(tag, ':'); if (!q || q - tag < 4) continue; *q++ = 0; while (*q == ' ' || *q == '\t') q++; x = '@' + (tag[0] & 0x1f); x = (x << 8) + '@' + (tag[1] & 0x1f); switch(x) { case 'A' << 8 | 'R': if (!strcasecmp(tag, "architecture")) s->arch = pool_str2id(pool, q, 1); break; case 'B' << 8 | 'R': if (!strcasecmp(tag, "breaks")) s->conflicts = makedeps(repo, q, s->conflicts, 0); break; case 'C' << 8 | 'O': if (!strcasecmp(tag, "conflicts")) s->conflicts = makedeps(repo, q, s->conflicts, 0); break; case 'D' << 8 | 'E': if (!strcasecmp(tag, "depends")) s->requires = makedeps(repo, q, s->requires, -SOLVABLE_PREREQMARKER); else if (!strcasecmp(tag, "description")) { char *ld = strchr(q, '\n'); if (ld) { *ld++ = 0; repodata_set_str(data, s - pool->solvables, SOLVABLE_DESCRIPTION, ld); } else repodata_set_str(data, s - pool->solvables, SOLVABLE_DESCRIPTION, q); repodata_set_str(data, s - pool->solvables, SOLVABLE_SUMMARY, q); } break; case 'E' << 8 | 'N': if (!strcasecmp(tag, "enhances")) s->enhances = makedeps(repo, q, s->enhances, 0); break; case 'F' << 8 | 'I': if (!strcasecmp(tag, "filename")) repodata_set_location(data, s - pool->solvables, 0, 0, q); break; case 'H' << 8 | 'O': if (!strcasecmp(tag, "homepage")) repodata_set_str(data, s - pool->solvables, SOLVABLE_URL, q); break; case 'I' << 8 | 'N': if (!strcasecmp(tag, "installed-size")) repodata_set_num(data, s - pool->solvables, SOLVABLE_INSTALLSIZE, strtoull(q, 0, 10) << 10); break; case 'M' << 8 | 'D': if (!strcasecmp(tag, "md5sum") && !checksumtype && strlen(q) == 16 * 2) { strcpy(checksum, q); checksumtype = REPOKEY_TYPE_MD5; } break; case 'M' << 8 | 'U': if (!strcasecmp(tag, "multi-arch")) repodata_set_poolstr(data, s - pool->solvables, SOLVABLE_MULTIARCH, q); break; case 'P' << 8 | 'A': if (!strcasecmp(tag, "package")) s->name = pool_str2id(pool, q, 1); break; case 'P' << 8 | 'R': if (!strcasecmp(tag, "pre-depends")) s->requires = makedeps(repo, q, s->requires, SOLVABLE_PREREQMARKER); else if (!strcasecmp(tag, "provides")) s->provides = makedeps(repo, q, s->provides, 0); break; case 'R' << 8 | 'E': if (!strcasecmp(tag, "replaces")) s->obsoletes = makedeps(repo, q, s->obsoletes, 0); else if (!strcasecmp(tag, "recommends")) s->recommends = makedeps(repo, q, s->recommends, 0); break; case 'S' << 8 | 'H': newtype = solv_chksum_str2type(tag); if (!newtype || solv_chksum_len(newtype) * 2 != strlen(q)) break; if (!checksumtype || (newtype == REPOKEY_TYPE_SHA1 && checksumtype != REPOKEY_TYPE_SHA256) || newtype == REPOKEY_TYPE_SHA256) { strcpy(checksum, q); checksumtype = newtype; } break; case 'S' << 8 | 'O': if (!strcasecmp(tag, "source")) { char *q2; /* ignore version for now */ for (q2 = q; *q2; q2++) if (*q2 == ' ' || *q2 == '\t') { *q2 = 0; break; } if (s->name && !strcmp(q, pool_id2str(pool, s->name))) repodata_set_void(data, s - pool->solvables, SOLVABLE_SOURCENAME); else repodata_set_id(data, s - pool->solvables, SOLVABLE_SOURCENAME, pool_str2id(pool, q, 1)); havesource = 1; } break; case 'S' << 8 | 'T': if (!strcasecmp(tag, "status")) repodata_set_poolstr(data, s - pool->solvables, SOLVABLE_INSTALLSTATUS, q); break; case 'S' << 8 | 'U': if (!strcasecmp(tag, "suggests")) s->suggests = makedeps(repo, q, s->suggests, 0); break; case 'V' << 8 | 'E': if (!strcasecmp(tag, "version")) s->evr = pool_str2id(pool, q, 1); break; } } if (checksumtype) repodata_set_checksum(data, s - pool->solvables, SOLVABLE_CHECKSUM, checksumtype, checksum); if (!s->arch) s->arch = ARCH_ALL; if (!s->evr) s->evr = ID_EMPTY; if (s->name) s->provides = repo_addid_dep(repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0); if (s->name && !havesource) repodata_set_void(data, s - pool->solvables, SOLVABLE_SOURCENAME); if (s->obsoletes) { /* obsoletes only count when the packages also conflict */ /* XXX: should not transcode here */ int i, j, k; Id d, cid; for (i = j = s->obsoletes; (d = repo->idarraydata[i]) != 0; i++) { if (!s->conflicts) continue; for (k = s->conflicts; (cid = repo->idarraydata[k]) != 0; k++) { if (repo->idarraydata[k] == cid) break; if (ISRELDEP(cid)) { Reldep *rd = GETRELDEP(pool, cid); if (rd->flags < 8 && rd->name == d) break; /* specialize obsoletes */ } } if (cid) repo->idarraydata[j++] = cid; } repo->idarraydata[j] = 0; if (j == s->obsoletes) s->obsoletes = 0; } } int repo_add_debpackages(Repo *repo, FILE *fp, int flags) { Pool *pool = repo->pool; Repodata *data; char *buf, *p; int bufl, l, ll; Solvable *s; data = repo_add_repodata(repo, flags); buf = solv_malloc(4096); bufl = 4096; l = 0; buf[l] = 0; p = buf; for (;;) { if (!(p = strchr(p, '\n'))) { int l3; while (l + 1024 >= bufl) { buf = solv_realloc(buf, bufl + 4096); bufl += 4096; } p = buf + l; ll = fread(p, 1, bufl - l - 1, fp); if (ll <= 0) break; p[ll] = 0; while ((l3 = strlen(p)) < ll) p[l3] = '\n'; l += ll; if (p != buf) p--; continue; } p++; if (*p != '\n') continue; *p = 0; ll = p - buf + 1; s = pool_id2solvable(pool, repo_add_solvable(repo)); control2solvable(s, data, buf); if (!s->name) s = solvable_free(s, 1); if (l > ll) memmove(buf, p + 1, l - ll); l -= ll; p = buf; buf[l] = 0; } if (l) { s = pool_id2solvable(pool, repo_add_solvable(repo)); control2solvable(s, data, buf); if (!s->name) s = solvable_free(s, 1); } solv_free(buf); if (!(flags & REPO_NO_INTERNALIZE)) repodata_internalize(data); return 0; } int repo_add_debdb(Repo *repo, int flags) { FILE *fp; const char *path = "/var/lib/dpkg/status"; if (flags & REPO_USE_ROOTDIR) path = pool_prepend_rootdir_tmp(repo->pool, path); if ((fp = fopen(path, "r")) == 0) return pool_error(repo->pool, -1, "%s: %s", path, strerror(errno)); repo_add_debpackages(repo, fp, flags); fclose(fp); return 0; } #define CONTROL_COMP_NONE 0 #define CONTROL_COMP_GZIP 1 #define CONTROL_COMP_XZ 2 #define CONTROL_COMP_ZSTD 3 Id repo_add_deb(Repo *repo, const char *deb, int flags) { Pool *pool = repo->pool; Repodata *data; unsigned char buf[4096], *bp; int l, l2, vlen, clen, ctarlen; int control_comp; unsigned char *ctgz; unsigned char pkgid[16]; unsigned char *ctar; int gotpkgid; FILE *fp; Solvable *s; struct stat stb; data = repo_add_repodata(repo, flags); if ((fp = fopen(flags & REPO_USE_ROOTDIR ? pool_prepend_rootdir_tmp(pool, deb) : deb, "r")) == 0) { pool_error(pool, -1, "%s: %s", deb, strerror(errno)); return 0; } if (fstat(fileno(fp), &stb)) { pool_error(pool, -1, "fstat: %s", strerror(errno)); fclose(fp); return 0; } l = fread(buf, 1, sizeof(buf), fp); if (l < 8 + 60 || (strncmp((char *)buf, "!\ndebian-binary ", 8 + 16) != 0 && strncmp((char *)buf, "!\ndebian-binary/ ", 8 + 16) != 0)) { pool_error(pool, -1, "%s: not a deb package", deb); fclose(fp); return 0; } vlen = atoi((char *)buf + 8 + 48); if (vlen < 0 || vlen > l) { pool_error(pool, -1, "%s: not a deb package", deb); fclose(fp); return 0; } vlen += vlen & 1; if (l < 8 + 60 + vlen + 60) { pool_error(pool, -1, "%s: unhandled deb package", deb); fclose(fp); return 0; } control_comp = 0; if (!strncmp((char *)buf + 8 + 60 + vlen, "control.tar.gz ", 16) || !strncmp((char *)buf + 8 + 60 + vlen, "control.tar.gz/ ", 16)) control_comp = CONTROL_COMP_GZIP; else if (!strncmp((char *)buf + 8 + 60 + vlen, "control.tar.xz ", 16) || !strncmp((char *)buf + 8 + 60 + vlen, "control.tar.xz/ ", 16)) control_comp = CONTROL_COMP_XZ; else if (!strncmp((char *)buf + 8 + 60 + vlen, "control.tar.zst ", 16) || !strncmp((char *)buf + 8 + 60 + vlen, "control.tar.zst/", 16)) control_comp = CONTROL_COMP_ZSTD; else if (!strncmp((char *)buf + 8 + 60 + vlen, "control.tar ", 16) || !strncmp((char *)buf + 8 + 60 + vlen, "control.tar/ ", 16)) control_comp = CONTROL_COMP_NONE; else { pool_error(pool, -1, "%s: control.tar is not second entry", deb); fclose(fp); return 0; } /* dpkg has no actual maximum size for the control.tar member, so this * just keeps from allocating arbitrarily large amounts of memory. */ clen = atoi((char *)buf + 8 + 60 + vlen + 48); if (clen <= 0 || clen >= MAX_CONTROL_SIZE) { pool_error(pool, -1, "%s: control.tar has illegal size", deb); fclose(fp); return 0; } ctgz = solv_calloc(1, clen + 4); bp = buf + 8 + 60 + vlen + 60; l -= 8 + 60 + vlen + 60; if (l > clen) l = clen; if (l) memcpy(ctgz, bp, l); if (l < clen) { if (fread(ctgz + l, clen - l, 1, fp) != 1) { pool_error(pool, -1, "%s: unexpected EOF", deb); solv_free(ctgz); fclose(fp); return 0; } } fclose(fp); gotpkgid = 0; if (flags & DEBS_ADD_WITH_PKGID) { Chksum *chk = solv_chksum_create(REPOKEY_TYPE_MD5); solv_chksum_add(chk, ctgz, clen); solv_chksum_free(chk, pkgid); gotpkgid = 1; } ctar = 0; if (control_comp == CONTROL_COMP_GZIP) ctar = decompress_gz(ctgz, clen, &ctarlen, MAX_CONTROL_SIZE); else if (control_comp == CONTROL_COMP_XZ) ctar = decompress_xz(ctgz, clen, &ctarlen, MAX_CONTROL_SIZE); else if (control_comp == CONTROL_COMP_ZSTD) ctar = decompress_zstd(ctgz, clen, &ctarlen, MAX_CONTROL_SIZE); else { ctarlen = clen; ctar = solv_memdup(ctgz, clen); } solv_free(ctgz); if (!ctar) { pool_error(pool, -1, "%s: control.tar decompression error", deb); return 0; } bp = ctar; l = ctarlen; l2 = 0; while (l > 512) { int j; l2 = 0; for (j = 124; j < 124 + 12; j++) if (bp[j] >= '0' && bp[j] <= '7') l2 = l2 * 8 + (bp[j] - '0'); if (l2 < 0 || l2 > l) { l2 = 0; break; } bp[124] = 0; if (!strcmp((char *)bp, "./control") || !strcmp((char *)bp, "control")) break; l2 = 512 + ((l2 + 511) & ~511); l -= l2; bp += l2; } if (l <= 512 || l - 512 - l2 <= 0 || l2 <= 0) { pool_error(pool, -1, "%s: control.tar contains no control file", deb); free(ctar); return 0; } memmove(ctar, bp + 512, l2); ctar = solv_realloc(ctar, l2 + 1); ctar[l2] = 0; s = pool_id2solvable(pool, repo_add_solvable(repo)); control2solvable(s, data, (char *)ctar); if (!(flags & REPO_NO_LOCATION)) repodata_set_location(data, s - pool->solvables, 0, 0, deb); if (S_ISREG(stb.st_mode)) repodata_set_num(data, s - pool->solvables, SOLVABLE_DOWNLOADSIZE, (unsigned long long)stb.st_size); if (gotpkgid) repodata_set_bin_checksum(data, s - pool->solvables, SOLVABLE_PKGID, REPOKEY_TYPE_MD5, pkgid); solv_free(ctar); if (!(flags & REPO_NO_INTERNALIZE)) repodata_internalize(data); return s - pool->solvables; } void pool_deb_get_autoinstalled(Pool *pool, FILE *fp, Queue *q, int flags) { Id name = 0, arch = 0; int autoinstalled = -1; char *buf, *bp; int x, l, bufl, eof = 0; Id p, pp; queue_empty(q); buf = solv_malloc(4096); bufl = 4096; l = 0; while (!eof) { while (bufl - l < 1024) { bufl += 4096; if (bufl > 1024 * 64) break; /* hmm? */ buf = solv_realloc(buf, bufl); } if (!fgets(buf + l, bufl - l, fp)) { eof = 1; buf[l] = '\n'; buf[l + 1] = 0; } l = strlen(buf); if (l && buf[l - 1] == '\n') buf[--l] = 0; if (!*buf || eof) { l = 0; if (name && autoinstalled > 0) { if ((flags & GET_USERINSTALLED_NAMEARCH) != 0) queue_push2(q, name, arch); else if ((flags & GET_USERINSTALLED_NAMES) != 0) queue_push(q, name); else { FOR_PROVIDES(p, pp, name) { Solvable *s = pool->solvables + p; if (s->name != name) continue; if (arch && s->arch != arch) continue; queue_push(q, p); } } } name = arch = 0; autoinstalled = -1; continue; } /* strip trailing space */ while (l && (buf[l - 1] == ' ' || buf[l - 1] == '\t')) buf[--l] = 0; l = 0; bp = strchr(buf, ':'); if (!bp || bp - buf < 4) continue; *bp++ = 0; while (*bp == ' ' || *bp == '\t') bp++; x = '@' + (buf[0] & 0x1f); x = (x << 8) + '@' + (buf[1] & 0x1f); switch(x) { case 'P' << 8 | 'A': if (!strcasecmp(buf, "package")) name = pool_str2id(pool, bp, 1); break; case 'A' << 8 | 'R': if (!strcasecmp(buf, "architecture")) arch = pool_str2id(pool, bp, 1); break; case 'A' << 8 | 'U': if (!strcasecmp(buf, "auto-installed")) autoinstalled = atoi(bp); break; default: break; } } solv_free(buf); }