[packages/poldek] Up to 0.46.0 git snapshot 02e02fb9 with trurlib c8c3443 and tndb 80e426f
arekm
arekm at pld-linux.org
Fri Oct 23 18:30:15 CEST 2026
commit 4aeb92834357540e7a0f8b9e4a72effdb289c3a0
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date: Fri Sep 18 23:46:40 2026 +0200
Up to 0.46.0 git snapshot 02e02fb9 with trurlib c8c3443 and tndb 80e426f
Drop patches merged upstream.
poldek-config.patch | 11 +-
poldek-dup-sources.patch | 143 --------
poldek-env-columns-lines.patch | 65 ----
poldek-ftp-pasv-crash.patch | 47 ---
poldek-ftp-pasv-parse.patch | 19 -
poldek-global-ignore-merges.patch | 193 ----------
poldek-multilib-bare-name-install.patch | 247 -------------
poldek-nocolor-cmp.patch | 56 ---
poldek-scoring-evr.patch | 101 ------
poldek-search-i-opt.patch | 138 --------
poldek-url-keep-port.patch | 50 ---
poldek-vcn-pool-reuse.patch | 53 ---
poldek-vfile-net-tests.patch | 603 --------------------------------
poldek-vfstat-redirect-crash.patch | 27 --
poldek.spec | 107 +++---
15 files changed, 59 insertions(+), 1801 deletions(-)
---
diff --git a/poldek.spec b/poldek.spec
index ce36d8b..9d2d1e3 100644
--- a/poldek.spec
+++ b/poldek.spec
@@ -22,18 +22,22 @@
%define ver_rpm 1:4.14
%endif
-%define rel 12
+%define gitref 02e02fb938d1e4c8228012d4d1f1c90aa7d26832
+%define snap 20261023
+%define rel 0.1
+# poldek's autogen.sh and CI build against trurlib and tndb master
+%define trurlib_gitref c8c3443df37a175f68ac9f0c55978dbb6d238c33
+%define tndb_gitref 80e426fe181798cc2780e6ca18f30ad8b69297b0
Summary: RPM packages management helper tool
Summary(hu.UTF-8): RPM csomagkezelést segítő eszköz
Summary(pl.UTF-8): Pomocnicze narzędzie do zarządzania pakietami RPM
Name: poldek
-Version: 0.45.0
-Release: %{rel}%{?with_snap:.%{SNAP}}
+Version: 0.46.0
+Release: 0.%{snap}.%{rel}%{?with_snap:.%{SNAP}}
License: GPL v2
Group: Applications/System
-#Source0: http://poldek.pld-linux.org/download/snapshots/%{name}-%{version}-cvs%{snap}.tar.bz2
-Source0: https://github.com/poldek-pm/poldek/releases/download/v%{version}/%{name}-%{version}.tar.xz
-# Source0-md5: 445033ef4d1312718d27b4b7fc197643
+Source0: https://github.com/poldek-pm/poldek/archive/%{gitref}/%{name}-%{snap}.tar.gz
+# Source0-md5: d5b585755b7683948894f378a7345ea6
Source1: %{name}.conf
Source2: %{name}-multilib.conf
Source3: %{name}-config.sh
@@ -45,25 +49,16 @@ Source8: %{name}-debuginfo.conf
Source9: %{name}-aidath.conf
Source10: %{name}-multilib-aidath.conf
Source11: %{name}-archive.conf
+Source12: https://github.com/poldek-pm/trurlib/archive/%{trurlib_gitref}/trurlib-%{trurlib_gitref}.tar.gz
+# Source12-md5: bc8baff5e9c04e59e468b1284e2ae341
+Source13: https://github.com/poldek-pm/tndb/archive/%{tndb_gitref}/tndb-%{tndb_gitref}.tar.gz
+# Source13-md5: 250fc79aed3258dfa6c336a40d571978
Source100: %{name}-snap.conf
Source101: %{name}-multilib-snap.conf
Source102: %{name}-debuginfo-snap.conf
Patch0: %{name}-config.patch
Patch1: pm-hooks.patch
Patch2: %{name}-ext-down-enable.patch
-Patch3: %{name}-search-i-opt.patch
-Patch4: %{name}-nocolor-cmp.patch
-Patch5: %{name}-multilib-bare-name-install.patch
-Patch6: %{name}-dup-sources.patch
-Patch7: %{name}-env-columns-lines.patch
-Patch8: %{name}-scoring-evr.patch
-Patch9: %{name}-global-ignore-merges.patch
-Patch10: %{name}-ftp-pasv-crash.patch
-Patch11: %{name}-ftp-pasv-parse.patch
-Patch12: %{name}-vfstat-redirect-crash.patch
-Patch13: %{name}-vcn-pool-reuse.patch
-Patch14: %{name}-url-keep-port.patch
-Patch15: %{name}-vfile-net-tests.patch
URL: http://poldek.pld-linux.org/
%{?with_rpm5:BuildRequires: %{db_pkg}-devel >= %{ver_db}}
BuildRequires: autoconf >= 2.63
@@ -72,10 +67,13 @@ BuildRequires: bzip2-devel
BuildRequires: check-devel
BuildRequires: docbook-dtd412-xml
BuildRequires: docbook2X
+BuildRequires: gettext-autopoint
BuildRequires: gettext-tools >= 0.11.5
BuildRequires: libgomp-devel
BuildRequires: libtool
BuildRequires: libxml2-devel >= 2
+BuildRequires: libxml2-progs
+BuildRequires: libxslt-progs
BuildRequires: lua54-devel
BuildRequires: ncurses-devel
BuildRequires: openssl-devel >= 0.9.7d
@@ -235,33 +233,31 @@ Python modulok poldek-hez.
Moduły języka Python dla poldka.
%prep
-%setup -q
+%setup -q -n %{name}-%{gitref} -a12 -a13
+rmdir trurlib tndb
+%{__mv} trurlib-%{trurlib_gitref} trurlib
+%{__mv} tndb-%{tndb_gitref} tndb
+
+# conf/*.conf are generated from doc/poldek.conf.xml and make regenerates them
+# when the xml is newer, so generate them after any patch to the xml and apply
+# the PLD defaults (Patch0-2) to the result
+cd conf
+for c in fetch source poldek; do
+ %{__sh} ../doc/conf-xml2.sh conf ../doc/poldek.conf.xml $c
+done
+cd ..
%patch -P0 -p1
%patch -P1 -p1
%patch -P2 -p1
-%patch -P3 -p1
-%patch -P4 -p1
-%patch -P5 -p1
-%patch -P6 -p1
-%patch -P7 -p1
-%patch -P8 -p1
-%patch -P9 -p1
-%patch -P10 -p1
-%patch -P11 -p1
-%patch -P12 -p1
-%patch -P13 -p1
-%patch -P14 -p1
-%patch -P15 -p1
-
-%{__rm} doc/poldek.info
-%{__rm} m4/libtool.m4 m4/lt*.m4
+
+# what upstream autogen.sh does besides autotools
+%{__make} -f Makefile.extra POTFILES_in
# cleanup backups after patching
find . '(' -name '*~' -o -name '*.orig' ')' -print0 | xargs -0 -r -l512 rm -f
-chmod u+x ./configure ./doc/conf-xml2.sh
%build
-%{__gettextize}
+%{__autopoint}
%{__libtoolize}
%{__aclocal} -I m4
%{__autoheader}
@@ -289,7 +285,7 @@ cd ..
%{?with_python:--with-python}
%{__make}
-%{__make} -C doc poldek.info
+%{__make} -C doc manual.html poldek.info
%if %{with python}
%{__make} -C python
@@ -433,6 +429,7 @@ cp -p %{SOURCE7} $RPM_BUILD_ROOT%{_pixmapsdir}/%{name}.png
%{__rm} -rf configs
cp -a conf configs
%{__rm} -f configs/Makefile*
+%{__rm} configs/.gitignore
%if %{with python}
%py_postclean
@@ -545,26 +542,26 @@ fi
%if %{without static}
%files libs
%defattr(644,root,root,755)
-%attr(755,root,root) %{_libdir}/libpoclidek.so.*.*.*
-%attr(755,root,root) %{_libdir}/libpoldek.so.*.*.*
-%attr(755,root,root) %{_libdir}/libtndb.so.*.*.*
-%attr(755,root,root) %{_libdir}/libtrurl.so.*.*.*
-%attr(755,root,root) %{_libdir}/libvfile.so.*.*.*
-%attr(755,root,root) %ghost %{_libdir}/libpoclidek.so.2
-%attr(755,root,root) %ghost %{_libdir}/libpoldek.so.4
-%attr(755,root,root) %ghost %{_libdir}/libtndb.so.0
-%attr(755,root,root) %ghost %{_libdir}/libtrurl.so.10
-%attr(755,root,root) %ghost %{_libdir}/libvfile.so.0
+%{_libdir}/libpoclidek.so.*.*.*
+%{_libdir}/libpoldek.so.*.*.*
+%{_libdir}/libtndb.so.*.*.*
+%{_libdir}/libtrurl.so.*.*.*
+%{_libdir}/libvfile.so.*.*.*
+%ghost %{_libdir}/libpoclidek.so.2
+%ghost %{_libdir}/libpoldek.so.4
+%ghost %{_libdir}/libtndb.so.0
+%ghost %{_libdir}/libtrurl.so.10
+%ghost %{_libdir}/libvfile.so.0
%endif
%files devel
%defattr(644,root,root,755)
%if %{without static}
-%attr(755,root,root) %{_libdir}/libpoclidek.so
-%attr(755,root,root) %{_libdir}/libpoldek.so
-%attr(755,root,root) %{_libdir}/libtndb.so
-%attr(755,root,root) %{_libdir}/libtrurl.so
-%attr(755,root,root) %{_libdir}/libvfile.so
+%{_libdir}/libpoclidek.so
+%{_libdir}/libpoldek.so
+%{_libdir}/libtndb.so
+%{_libdir}/libtrurl.so
+%{_libdir}/libvfile.so
%endif
%{_libdir}/libpoclidek.la
%{_libdir}/libpoldek.la
@@ -586,7 +583,7 @@ fi
%if %{with python}
%files -n python-poldek
%defattr(644,root,root,755)
-%attr(755,root,root) %{py_sitedir}/_poldekmod.so
+%{py_sitedir}/_poldekmod.so
%{py_sitescriptdir}/poldek.py[co]
%{py_sitescriptdir}/poldekmod.py[co]
%endif
diff --git a/poldek-config.patch b/poldek-config.patch
index f89f304..05e13a8 100644
--- a/poldek-config.patch
+++ b/poldek-config.patch
@@ -1,13 +1,16 @@
---- poldek-0.30.0/conf/poldek.conf 2013-07-08 14:19:35.294351501 +0300
-+++ poldek-0.30.0/conf/poldek.conf 2013-07-08 14:06:38.722878665 +0300
-@@ -132,12 +132,11 @@
+--- a/conf/poldek.conf
++++ b/conf/poldek.conf
+@@ -135,15 +135,14 @@
# Prevent package listed from being upgraded if they are already installed.
# hold = kernel* foo*.i686 th-test:* *-smp-* th-ready:bar*.x86_64
-#hold =
+hold = kernel*
- # Ignore package list - packages fits given mask will be invisible.
+ # Ignore package list - packages matching given masks will be invisible.
+ # Patterns listed here apply to every source; any
+ # per-source ignore entry adds more masks on top of this
+ # global list rather than replacing it.
# ignore = *-smp-* foo*
-# ignore = vserver-packages
-#ignore = vserver-packages
diff --git a/poldek-dup-sources.patch b/poldek-dup-sources.patch
deleted file mode 100644
index f98da4a..0000000
--- a/poldek-dup-sources.patch
+++ /dev/null
@@ -1,143 +0,0 @@
-commit bd97788fa990b4b2cd957c540399cec35afab526
-Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
-Date: Tue Apr 14 16:26:11 2026 +0200
-
- Show config file origin when removing duplicated sources
-
- Print which config file:line each duplicated source was defined at,
- making it easy to find and fix the duplication.
-
- Also fix conf.c filemark formatting: remove trailing colon from
- stored __file__line value and format strings to avoid double-colon
- artifacts in error messages.
-
-diff --git a/conf.c b/conf.c
-index 83e625f6..f9f775b0 100644
---- a/conf.c
-+++ b/conf.c
-@@ -505,7 +505,7 @@ static int verify_param_presence(tn_hash *ht_sect, const char *section,
-
- if (overwrite || (tag->flags & CONF_TYPE_F_MULTI_EXCL)) {
- if (!overwrite || poldek_VERBOSE > 1)
-- logn(LOGWARN, _("%s %s::%s redefined"), filemark, section, name);
-+ logn(LOGWARN, _("%s: %s::%s redefined"), filemark, section, name);
- n_hash_remove(ht_sect, name);
-
- } else if ((tag->flags & CONF_TYPE_F_MULTI) == 0) {
-@@ -541,9 +541,9 @@ static int add_param(tn_hash *ht_sect, const char *section,
- validate = (flags & ADD_PARAM_VALIDATE);
-
- if (path)
-- n_snprintf(filemark, sizeof(filemark), "%s:%d:", path, nline);
-+ n_snprintf(filemark, sizeof(filemark), "%s:%d", path, nline);
- else
-- n_snprintf(filemark, sizeof(filemark), "config:");
-+ n_snprintf(filemark, sizeof(filemark), "config");
-
- if ((tagindex = find_tag(section, name, §)) == -1) {
- if (*name == '_') /* internal or _macro */
-@@ -554,7 +554,7 @@ static int add_param(tn_hash *ht_sect, const char *section,
- tag = &unknown_tag;
-
- } else {
-- logn(LOGWARN, _("%s unknown option '%s::%s'"), filemark,
-+ logn(LOGWARN, _("%s: unknown option '%s::%s'"), filemark,
- section, name);
- return 0;
- }
-@@ -604,7 +604,7 @@ static int add_param(tn_hash *ht_sect, const char *section,
- val = "";
-
- if (val == NULL) {
-- logn(LOGERR, _("%s invalid value of '%s::%s'"), filemark, section, name);
-+ logn(LOGERR, _("%s: invalid value of '%s::%s'"), filemark, section, name);
- return 0;
- }
-
-@@ -618,7 +618,7 @@ static int add_param(tn_hash *ht_sect, const char *section,
- }
-
- if (!valid) {
-- logn(LOGWARN, _("%s invalid value '%s' of '%s::%s'"), filemark,
-+ logn(LOGWARN, _("%s: invalid value '%s' of '%s::%s'"), filemark,
- val, section, name);
- return 0;
- }
-@@ -826,7 +826,7 @@ static tn_hash *open_section_ht(tn_hash *htconf,
- char filemark[PATH_MAX];
- struct copt *opt;
-
-- n_snprintf(filemark, sizeof(filemark), "%s:%d:", path, nline);
-+ n_snprintf(filemark, sizeof(filemark), "%s:%d", path, nline);
- opt = copt_new("__file__line");
- opt->val = n_strdup(filemark);
- n_hash_insert(ht_sect, opt->name, opt);
-diff --git a/pkgdir/source.c b/pkgdir/source.c
-index 28c0049f..8d23592d 100644
---- a/pkgdir/source.c
-+++ b/pkgdir/source.c
-@@ -176,6 +176,7 @@ struct source *source_malloc(void)
- //src->flags |= PKGSOURCE_PRI;
- src->name = src->path = src->pkg_prefix = NULL;
- src->group = src->dscr = NULL;
-+ src->config_origin = NULL;
- src->lc_lang = NULL;
- src->_refcnt = 0;
- src->exclude_path = n_array_new(4, free, (tn_fn_cmp)strcmp);
-@@ -213,6 +214,7 @@ struct source *source_clone(const struct source *src)
- cp_str_ifnotnull(&nsrc->group, src->group);
- cp_str_ifnotnull(&nsrc->lc_lang, src->lc_lang);
- cp_str_ifnotnull(&nsrc->original_type, src->original_type);
-+ cp_str_ifnotnull(&nsrc->config_origin, src->config_origin);
-
- n_array_free(nsrc->exclude_path);
- nsrc->exclude_path = n_ref(src->exclude_path);
-@@ -241,6 +243,7 @@ void source_free(struct source *src)
- n_cfree(&src->group);
- n_cfree(&src->lc_lang);
- n_cfree(&src->original_type);
-+ n_cfree(&src->config_origin);
-
- if (src->exclude_path)
- n_array_free(src->exclude_path);
-@@ -662,6 +665,10 @@ struct source *source_new_htcnf(const tn_hash *htcnf)
- if (vs)
- src->original_type = n_strdup(vs);
-
-+ vs = poldek_conf_get(htcnf, "__file__line", NULL);
-+ if (vs)
-+ src->config_origin = n_strdup(vs);
-+
- get_conf_opt_list(htcnf, "exclude path", src->exclude_path);
- get_conf_opt_list(htcnf, "ignore", src->ign_patterns);
- return src;
-@@ -687,11 +694,16 @@ int source_cmp_uniq(const struct source *s1, const struct source *s2)
- rc = strcmp(n1, n2);
- }
-
-- if (rc == 0)
-+ if (rc == 0) {
- logn(LOGWARN, _("removed duplicated source %s%s%s"),
- (s2->flags & PKGSOURCE_NAMED) ? s2->name : "",
- (s2->flags & PKGSOURCE_NAMED) ? " -- " : "",
- s2->path);
-+ if (s1->config_origin || s2->config_origin)
-+ logn(LOGWARN, _(" defined at %s and %s"),
-+ s1->config_origin ? s1->config_origin : "(unknown)",
-+ s2->config_origin ? s2->config_origin : "(unknown)");
-+ }
-
- return rc;
- }
-diff --git a/pkgdir/source.h b/pkgdir/source.h
-index 6052ec25..b93639b5 100644
---- a/pkgdir/source.h
-+++ b/pkgdir/source.h
-@@ -55,6 +55,7 @@ struct source {
- tn_array *exclude_path;
- tn_array *ign_patterns; /* ignore package patterns */
- char *original_type; /* type of source repo for this source */
-+ char *config_origin; /* config file:line where source was defined */
- unsigned subopt_flags;
- int _refcnt;
- char *group;
diff --git a/poldek-env-columns-lines.patch b/poldek-env-columns-lines.patch
deleted file mode 100644
index 89198e5..0000000
--- a/poldek-env-columns-lines.patch
+++ /dev/null
@@ -1,65 +0,0 @@
-commit bcf31cc1653155c93ad4b2b1a17d2301ac70d36b
-Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
-Date: Tue Apr 14 17:45:51 2026 +0200
-
- Use $COLUMNS as fallback when ioctl fails to get terminal width
-
- When stdout is not a tty (e.g. piped output), ioctl TIOCGWINSZ fails
- and terminal width falls back to 80 columns, causing unnecessary URL
- truncation. Check $COLUMNS environment variable before falling back
- to the default.
-
-diff --git a/poldek_term.c b/poldek_term.c
-index 9d4f9a87..6fbf7d82 100644
---- a/poldek_term.c
-+++ b/poldek_term.c
-@@ -245,15 +245,34 @@ static void update_term_width(void)
-
- if (winch_reached) {
- char tmp[256];
-+ term_width = TERM_DEFAULT_WIDTH;
-+ term_height = TERM_DEFAULT_HEIGHT;
-+
- if (ioctl(1, TIOCGWINSZ, &ws) == 0) {
- term_width = ws.ws_col;
- term_height = ws.ws_row;
-
- } else {
-- term_width = TERM_DEFAULT_WIDTH;
-- term_height = TERM_DEFAULT_HEIGHT;
-+ const char *cols = getenv("COLUMNS");
-+ if (cols) {
-+ int n = atoi(cols);
-+ if (n > 0)
-+ term_width = n;
-+ }
-+
-+ const char *rows = getenv("LINES");
-+ if (rows) {
-+ int n = atoi(rows);
-+ if (n > 0)
-+ term_height = n;
-+ }
- }
-
-+ if (term_width < TERM_MIN_WIDTH)
-+ term_width = TERM_MIN_WIDTH;
-+ if (term_height < TERM_MIN_HEIGHT)
-+ term_height = TERM_MIN_HEIGHT;
-+
- //https://www.gnu.org/software/libc/manual/html_node/Argp-User-Customization.html
- snprintf(tmp, sizeof(tmp), "no-dup-args-note,rmargin=%d", term_width - 1);
- setenv("ARGP_HELP_FMT", tmp, 1);
-diff --git a/poldek_term.h b/poldek_term.h
-index 80e6d5a6..2b1db740 100644
---- a/poldek_term.h
-+++ b/poldek_term.h
-@@ -25,6 +25,8 @@
-
- #define TERM_DEFAULT_WIDTH 80
- #define TERM_DEFAULT_HEIGHT 24
-+#define TERM_MIN_WIDTH 40
-+#define TERM_MIN_HEIGHT 4
-
- #include <stddef.h> /* for size_t */
-
diff --git a/poldek-ftp-pasv-crash.patch b/poldek-ftp-pasv-crash.patch
deleted file mode 100644
index 14f8c0d..0000000
--- a/poldek-ftp-pasv-crash.patch
+++ /dev/null
@@ -1,47 +0,0 @@
- fix: do not crash when the FTP PASV reply cannot be read
-
- vftpcn_pasv() handed the server's response text to vfff_set_err() as the
- printf format string. When the reply cannot be read at all - a timeout
- or a reset control connection, which is exactly what a flaky link
- produces - vftpcn_resp() fails and resp_msg() is NULL, so vsnprintf()
- gets a NULL format and the process dies with SIGSEGV. A server that
- answers PASV with a message containing conversions is a format string
- bug on its own.
-
- Bail out on an unreadable response and keep the error readresp() has
- already set instead of overwriting it with a bare EIO, and print a
- non-conforming reply through "%s".
-
- Annotate vfff_set_err() as printf-like so -Wformat-security, already a
- build error here, rejects the next such call at compile time.
-
-diff --git a/vfile/vfff/ftp.c b/vfile/vfff/ftp.c
---- a/vfile/vfff/ftp.c
-+++ b/vfile/vfff/ftp.c
-@@ -556,8 +556,11 @@
- if (!vftpcn_cmd(cn, cmd))
- return 0;
-
-- if (!vftpcn_resp(cn) || resp_code(cn) != req_code) {
-- vfff_set_err(EIO, resp_msg(cn));
-+ if (!vftpcn_resp(cn)) /* no response at all; readresp() set the cause */
-+ return 0;
-+
-+ if (resp_code(cn) != req_code) {
-+ vfff_set_err(EIO, "%s", resp_msg(cn));
- return 0;
- }
-
-diff --git a/vfile/vfff/vfff.h b/vfile/vfff/vfff.h
---- a/vfile/vfff/vfff.h
-+++ b/vfile/vfff/vfff.h
-@@ -39,7 +39,8 @@
- extern void (*vfff_vlog_cb)(const char *fmt, va_list ap);
-
- const char *vfff_errmsg(void);
--void vfff_set_err(int err_no, const char *fmt, ...);
-+void vfff_set_err(int err_no, const char *fmt, ...)
-+ __attribute__((format(printf, 2, 3)));
- void vfff_log(const char *fmt, ...);
- int vfff_sigint_reached(void);
- int vfff_to_connect(const char *host, const char *service, int *af);
diff --git a/poldek-ftp-pasv-parse.patch b/poldek-ftp-pasv-parse.patch
deleted file mode 100644
index c58209b..0000000
--- a/poldek-ftp-pasv-parse.patch
+++ /dev/null
@@ -1,19 +0,0 @@
- fix: bound the PASV address scan
-
- parse_pasv() skipped to the first digit with an unbounded loop, so a 227
- reply whose text carries no digit at all reads past the end of the
- response buffer. Stopping at the terminator lets the sscanf() below
- fail and report a parse error like any other malformed reply.
-
-diff --git a/vfile/vfff/ftp.c b/vfile/vfff/ftp.c
---- a/vfile/vfff/ftp.c
-+++ b/vfile/vfff/ftp.c
-@@ -499,7 +499,7 @@
- is_err = 0;
-
- p = resp;
-- while (!isdigit(*p))
-+ while (*p && !isdigit(*p))
- p++;
-
- if (sscanf(p, "%d,%d,%d,%d,%d,%d", &a[0], &a[1], &a[2], &a[3],
diff --git a/poldek-global-ignore-merges.patch b/poldek-global-ignore-merges.patch
deleted file mode 100644
index e7776a7..0000000
--- a/poldek-global-ignore-merges.patch
+++ /dev/null
@@ -1,193 +0,0 @@
-commit aea999f2f1cb86579bc8fef9f5393120d12f863b
-Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
-Date: Wed Apr 22 09:50:32 2026 +0200
-
- fix: apply [global] + per-source ignore across all pkg paths
-
- [global] ignore = ... was silently replaced by any per-source
- ignore = ... entry rather than extended, so global patterns were
- lost on every source with its own ignore line. And the stub-index
- load path (poldek --cmd "ls", search, etc.) bypassed ignore
- filtering entirely for PKGDIR_CAP_HANDLEIGNORE modules -- that cap
- only covers the full do_load path, not the separate stub .zst
- cache loaded by load_stubindex.
-
- - lib_init.c: always concat [global] ign_patterns into src's.
- - lib_pkgset.c: in poldek_load_stubs, post-filter per-source stubs
- via src->ign_patterns, symmetric with the full-load path and
- gated by POLDEK_OP_IGNORE.
- - pkgdir/pkgdir_stubindex.c: source_stubload is now a pure loader;
- the HANDLEIGNORE guard there was dropping filtering entirely for
- pndir sources in the stub path.
-
-diff --git a/doc/poldek.conf.xml b/doc/poldek.conf.xml
-index 86cb545b..828bb683 100644
---- a/doc/poldek.conf.xml
-+++ b/doc/poldek.conf.xml
-@@ -395,7 +395,10 @@ and [filename]*-source.conf[/filename], file getters are declared in
-
- <option name="ignore" type="string" list="yes" default="vserver-packages" multiple="yes">
- <description>
-- Ignore package list - packages fits given mask will be invisible.
-+ Ignore package list - packages matching given masks will be invisible.
-+ Patterns listed here apply to every [option]source[/option]; any
-+ per-source [option]ignore[/option] entry adds more masks on top of this
-+ global list rather than replacing it.
- [screen]
- ignore = *-smp-* foo*
- ignore = vserver-packages
-@@ -634,7 +637,9 @@ Every repository is configured in its own [ source ] section.
-
- <option name="ignore" type="string" list="yes" default="" multiple="yes">
- <description>
-- Have the same meaning as [ global ] parameter. Example:
-+ Additional ignore masks for this source, merged with the [ global ]
-+ [option]ignore[/option] list (global patterns always apply; per-source
-+ entries add more). Example:
- [screen]
- ignore = kernel*smp* dev
- [/screen]
-diff --git a/lib_init.c b/lib_init.c
-index 297ec37e..88684654 100644
---- a/lib_init.c
-+++ b/lib_init.c
-@@ -245,12 +245,11 @@ struct source *do_source_new_htcnf(struct poldek_ctx *ctx,
- (tn_fn_dup)strdup);
- }
-
-- if (n_array_size(src->ign_patterns) == 0 && /* take global */
-- n_array_size(ctx->ts->ign_patterns) > 0) {
--
-- n_array_free(src->ign_patterns);
-- src->ign_patterns = n_array_dup(ctx->ts->ign_patterns,
-- (tn_fn_dup)strdup);
-+ if (n_array_size(ctx->ts->ign_patterns) > 0) {
-+ n_array_concat_ex(src->ign_patterns, ctx->ts->ign_patterns,
-+ (tn_fn_dup)strdup);
-+ n_array_sort(src->ign_patterns);
-+ n_array_uniq(src->ign_patterns);
- }
-
- return src;
-diff --git a/lib_pkgset.c b/lib_pkgset.c
-index 1b19752e..0b676fc9 100644
---- a/lib_pkgset.c
-+++ b/lib_pkgset.c
-@@ -129,6 +129,7 @@ tn_array *poldek_load_stubs(struct poldek_ctx *ctx)
-
- n_array_isort_ex(sources, (tn_fn_cmp)source_cmp_pri);
- tn_array *stubpkgs = pkgs_array_new(4096);
-+ int do_ignore = ctx->ts->getop(ctx->ts, POLDEK_OP_IGNORE);
-
- for (i=0; i < n_array_size(sources); i++) {
- struct source *src = n_array_nth(sources, i);
-@@ -145,6 +146,9 @@ tn_array *poldek_load_stubs(struct poldek_ctx *ctx)
- return 0;
- }
-
-+ if (do_ignore && n_array_size(src->ign_patterns) > 0)
-+ packages_score_ignore(pkgs, src->ign_patterns, 1);
-+
- while (n_array_size(pkgs) > 0) {
- struct pkg *pkg = n_array_shift(pkgs);
-
-diff --git a/pkgdir/pkgdir_stubindex.c b/pkgdir/pkgdir_stubindex.c
-index 7707378b..a7532896 100644
---- a/pkgdir/pkgdir_stubindex.c
-+++ b/pkgdir/pkgdir_stubindex.c
-@@ -204,13 +204,6 @@ tn_array *source_stubload(struct source *src)
- if (pkgs == NULL)
- return NULL;
-
-- if (src->ign_patterns) {
-- const struct pkgdir_module *mod = pkgdir_mod_find(src->type);
-- /* module does not handle "ignore" itself */
-- if (mod && (mod->cap_flags & PKGDIR_CAP_HANDLEIGNORE) == 0)
-- packages_score_ignore(pkgs, src->ign_patterns, 1);
-- }
--
- return pkgs;
- }
-
-diff --git a/tests/Makefile.am b/tests/Makefile.am
-index 04654c67..ff08ccb9 100644
---- a/tests/Makefile.am
-+++ b/tests/Makefile.am
-@@ -2,7 +2,7 @@ CPPFLAGS = @CPPFLAGS@ @CHECK_CFLAGS@
- LDADD = $(top_builddir)/libpoldek.la @CHECK_LIBS@
-
- check_PROGRAMS = test_match test_env test_pmdb test_op test_config \
-- test_store test_cmp test_booldeps
-+ test_store test_cmp test_booldeps test_ignore_merge
-
- TESTS = $(check_PROGRAMS)
-
-diff --git a/tests/test_ignore_merge.c b/tests/test_ignore_merge.c
-new file mode 100644
-index 00000000..97e061ed
---- /dev/null
-+++ b/tests/test_ignore_merge.c
-@@ -0,0 +1,63 @@
-+#include "test.h"
-+#include "poldek.h"
-+#include "pkgdir/source.h"
-+
-+static int list_contains(tn_array *list, const char *s)
-+{
-+ int i;
-+ for (i = 0; i < n_array_size(list); i++)
-+ if (n_str_eq((const char *)n_array_nth(list, i), s))
-+ return 1;
-+ return 0;
-+}
-+
-+START_TEST (test_ignore_merge) {
-+ const char *path = "poldek_test_ignore_merge.conf";
-+ FILE *f = fopen(path, "w");
-+ fail_if(f == NULL, "cannot open %s for write", path);
-+ fprintf(f,
-+ "[global]\n"
-+ "ignore = foo* baz*\n"
-+ "\n"
-+ "[source]\n"
-+ "name = testsrc\n"
-+ "type = pndir\n"
-+ "url = file:///tmp/poldek-test-nonexistent\n"
-+ "ignore = bar*\n");
-+ fclose(f);
-+
-+ poldeklib_init();
-+ struct poldek_ctx *ctx = poldek_new(0);
-+ fail_if(ctx == NULL, "poldek_new failed");
-+ fail_if(poldek_load_config(ctx, path, NULL, 0) == 0,
-+ "load_config failed");
-+ fail_if(poldek_setup(ctx) == 0, "poldek_setup failed");
-+
-+ tn_array *sources = poldek_get_sources(ctx);
-+ fail_if(sources == NULL, "no sources?");
-+
-+ struct source *src = NULL;
-+ int i;
-+ for (i = 0; i < n_array_size(sources); i++) {
-+ struct source *s = n_array_nth(sources, i);
-+ if (s->name && n_str_eq(s->name, "testsrc")) {
-+ src = s;
-+ break;
-+ }
-+ }
-+ fail_if(src == NULL, "testsrc not found in sources");
-+
-+ fail_unless(list_contains(src->ign_patterns, "foo*"),
-+ "global 'foo*' missing from merged ign_patterns");
-+ fail_unless(list_contains(src->ign_patterns, "baz*"),
-+ "global 'baz*' missing from merged ign_patterns");
-+ fail_unless(list_contains(src->ign_patterns, "bar*"),
-+ "per-source 'bar*' missing from merged ign_patterns");
-+
-+ n_array_free(sources);
-+ poldek_free(ctx);
-+ unlink(path);
-+}
-+END_TEST
-+
-+NTEST_RUNNER("ignore_merge", test_ignore_merge);
diff --git a/poldek-multilib-bare-name-install.patch b/poldek-multilib-bare-name-install.patch
deleted file mode 100644
index 13d269e..0000000
--- a/poldek-multilib-bare-name-install.patch
+++ /dev/null
@@ -1,247 +0,0 @@
-From 3d5addaec1f408f389f66e3ded712315f734158b Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Arkadiusz=20Mi=C5=9Bkiewicz?= <arekm at maven.pl>
-Date: Tue, 14 Apr 2026 13:59:26 +0200
-Subject: [PATCH 1/2] fix: in multilib mode, bare-name install should only
- install native arch
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Problem: on a multilib system with multiple architecture repos (e.g.
-th, th-i686, th-x86_64), running "install curl" resolves curl from ALL
-architectures and pulls in a massive foreign-arch dependency chain
-(40+ packages). It also emits a misleading "ambiguous name" warning.
-The user only wanted the native-arch package.
-
-Solution: add ARG_PACKAGES_RESOLV_PREFER_NATIVE flag and filter in
-resolve_masks() — the resolution layer where user masks are matched
-to packages. When the flag is set (install path only, not ls/search):
-
-- Bare-name matches (strcmp with pkg->name) skip non-native, non-noarch
- packages via the multilib_skip_foreign() helper
-- Glob/fnmatch matches are unaffected — "install curl*" installs all
-- Explicit names like "install curl-8.19.0-1.i686" go through fnmatch
- and are also unaffected
-
-This eliminates the false "ambiguous name" warning (matches_bycmp now
-only counts native-arch matches) and produces a helpful suggestion when
-no native arch exists at all. The no-native-arch case sets rc=0 (fails
-the transaction) like "no such package" does — the user must use an
-explicit name.
-
-The install validation function (poldek_ts_validate_args_with_stubs)
-has no multilib awareness — arch policy lives entirely in the
-resolution layer where it belongs.
-
-Examples:
- install curl -> installs only curl.x86_64 (native)
- install curl-8.19.0-1.i686 -> installs curl.i686 (explicit)
- install curl* -> installs all matching arches (glob)
- ls curl -> shows all arches (no PREFER_NATIVE)
----
- arg_packages.c | 68 +++++++++++++++++++++++++++++++++++++++++++++-----
- arg_packages.h | 4 +++
- 2 files changed, 66 insertions(+), 6 deletions(-)
-
-diff --git a/arg_packages.c b/arg_packages.c
-index 8b2d2cdb..d8def6b9 100644
---- a/arg_packages.c
-+++ b/arg_packages.c
-@@ -457,6 +457,31 @@ tn_array *resolve_bycap(struct arg_packages *aps, struct pkgset *ps,
- return pkgs;
- }
-
-+/*
-+ multilib: should this package be skipped for a bare-name mask match?
-+
-+ Problem: "install curl" on a multilib system resolves to curl.x86_64,
-+ curl.i686, curl.x32 — pulling in a massive foreign-arch dependency
-+ chain. The user just wanted the native-arch package.
-+
-+ This only applies to bare-name matches (strcmp with pkg->name).
-+ Explicit names ("install curl-1.0-1.i686") and globs ("install curl*")
-+ go through the fnmatch path and are not affected.
-+
-+ Returns true if the package should be skipped:
-+ - caller asked for PREFER_NATIVE (install path, not ls/search)
-+ - multilib mode is on
-+ - package is not noarch (noarch is always installable)
-+ - package arch score != 1 (not the native/base architecture)
-+*/
-+static int multilib_skip_foreign(const struct pkg *pkg, unsigned flags)
-+{
-+ return (flags & ARG_PACKAGES_RESOLV_PREFER_NATIVE) &&
-+ poldek_conf_MULTILIB &&
-+ !pkg_is_noarch(pkg) &&
-+ pkg_arch_score(pkg) != 1;
-+}
-+
- static
- int resolve_masks(tn_array *re,
- struct arg_packages *aps, tn_array *avpkgs,
-@@ -465,6 +490,7 @@ int resolve_masks(tn_array *re,
- {
- int i, j, nmasks, rc = 1;
- int *matches, *matches_bycmp;
-+ const char **first_foreign_id;
-
- nmasks = n_array_size(aps->package_masks);
-
-@@ -474,6 +500,12 @@ int resolve_masks(tn_array *re,
- matches_bycmp = alloca(nmasks * sizeof(*matches_bycmp));
- memset(matches_bycmp, 0, nmasks * sizeof(*matches_bycmp));
-
-+ /* first_foreign_id[j]: when a bare-name mask skips a foreign-arch
-+ package, remember its pkg_id (e.g. "curl-8.19.0-1.i686") so we
-+ can suggest it in the warning if no native arch exists at all */
-+ first_foreign_id = alloca(nmasks * sizeof(*first_foreign_id));
-+ memset(first_foreign_id, 0, nmasks * sizeof(*first_foreign_id));
-+
- for (i=0; i < n_array_size(avpkgs); i++) {
- struct pkg *pkg = n_array_nth(avpkgs, i);
-
-@@ -494,11 +526,16 @@ int resolve_masks(tn_array *re,
-
- DBGF("%s cmp %s or %s\n", mask, pkg->name, pkg_id(pkg));
- if (strcmp(mask, pkg->name) == 0) {
-- if (re)
-- n_array_push(re, pkg_link(pkg));
--
-- matches_bycmp[j]++;
-- matches[j]++;
-+ if (multilib_skip_foreign(pkg, flags)) {
-+ if (!first_foreign_id[j])
-+ first_foreign_id[j] = pkg_id(pkg);
-+ } else {
-+ if (re)
-+ n_array_push(re, pkg_link(pkg));
-+
-+ matches_bycmp[j]++;
-+ matches[j]++;
-+ }
-
- } else if (fnmatch(mask, pkg_id(pkg), 0) == 0) {
- if (re)
-@@ -521,6 +558,17 @@ int resolve_masks(tn_array *re,
- }
- }
-
-+ /* multilib: bare name matched only foreign-arch packages;
-+ fail like "no such package" — the user must be explicit */
-+ if (unmatched && first_foreign_id[j]) {
-+ if (!quiet)
-+ logn(LOGWARN, _("%s: no native arch package available;"
-+ " to install use explicit name, e.g.: %s"),
-+ mask, first_foreign_id[j]);
-+ rc = 0;
-+ continue;
-+ }
-+
- if (unmatched && (flags & ARG_PACKAGES_RESOLV_MISSINGOK) == 0) {
- if (!quiet)
- logn(LOGERR, _("%s: no such package"), mask);
-@@ -698,7 +746,15 @@ int arg_packages__validate_with_stubs(struct arg_packages *aps, tn_array *stubpk
- re = *resolved;
- }
-
-- if (!resolve_masks(re, aps, stubpkgs, NULL, 0, quiet))
-+ /* install path: in multilib mode, bare package names (e.g. "install curl")
-+ should resolve only to native-arch packages. The ls/search path
-+ (arg_packages_resolve) does not set this flag, so "ls curl" still
-+ shows all architectures. */
-+ unsigned resolve_flags = 0;
-+ if (poldek_conf_MULTILIB)
-+ resolve_flags |= ARG_PACKAGES_RESOLV_PREFER_NATIVE;
-+
-+ if (!resolve_masks(re, aps, stubpkgs, NULL, resolve_flags, quiet))
- return 0;
-
- if (n_array_size(aps->packages) > 0) {
-diff --git a/arg_packages.h b/arg_packages.h
-index cbeb163a..2de984c4 100644
---- a/arg_packages.h
-+++ b/arg_packages.h
-@@ -50,6 +50,10 @@ EXPORT int arg_packages_setup(struct arg_packages *aps, struct pm_ctx *ctx);
- #define ARG_PACKAGES_RESOLV_CAPSINLINE (1 << 4)/* add packages found by caps
- to resolved packages */
- #define ARG_PACKAGES_RESOLV_WARN_ONLY (1 << 5)/* warn only*/
-+#define ARG_PACKAGES_RESOLV_PREFER_NATIVE (1 << 6)/* multilib: "install curl"
-+ resolves only to native arch;
-+ "install curl-1.0-1.i686" and
-+ "install curl*" still match all */
-
- int arg_packages__validate_with_stubs(struct arg_packages *aps, tn_array *stubpkgs,
- tn_array **resolved, int quiet);
---
-2.53.0
-
-From e924bff4180e6fcba25d8c8d6e12ef5fbe582f52 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Arkadiusz=20Mi=C5=9Bkiewicz?= <arekm at maven.pl>
-Date: Tue, 14 Apr 2026 13:03:34 +0200
-Subject: [PATCH 2/2] test: add multilib bare-name vs explicit arch install
- tests
-
-Verify that bare-name install picks only native arch, explicit
-name-version.arch installs foreign arch, and glob patterns install
-all matching architectures.
----
- tests/sh/07-depsolver | 43 +++++++++++++++++++++++++++++++++++++++++++
- 1 file changed, 43 insertions(+)
-
-diff --git a/tests/sh/07-depsolver b/tests/sh/07-depsolver
-index 5c95e6b0..a4ca2d85 100755
---- a/tests/sh/07-depsolver
-+++ b/tests/sh/07-depsolver
-@@ -965,6 +965,49 @@ xtestUninstallHold() {
- try_uninstall a "a-1-1,a-devel-1-1"
- }
-
-+# bare-name install in multilib should only install native arch
-+testInstallBareNameMultilibNativeOnly() {
-+ ORIG_POLDEK_INSTALL="$POLDEK_INSTALL"
-+ POLDEK_INSTALL="$POLDEK_INSTALL -Omultilib=1"
-+
-+ msgn "Preparing repositories..."
-+ build a 1-1 -a x86_64 -f "/hello.x86_64"
-+ build a 1-1 -a i686 -f "/hello.i686"
-+
-+ msgn "Bare-name install should pick only native arch"
-+ try_install a "a-1-1.x86_64"
-+
-+ POLDEK_INSTALL="$ORIG_POLDEK_INSTALL"
-+}
-+
-+# explicit name-version.arch should install that specific arch
-+testInstallExplicitArchMultilib() {
-+ ORIG_POLDEK_INSTALL="$POLDEK_INSTALL"
-+ POLDEK_INSTALL="$POLDEK_INSTALL -Omultilib=1"
-+
-+ msgn "Preparing repositories..."
-+ build a 1-1 -a x86_64 -f "/hello.x86_64"
-+ build a 1-1 -a i686 -f "/hello.i686"
-
-+ msgn "Explicit arch should install that specific arch"
-+ try_install a-1-1.i686 "a-1-1.i686"
-+
-+ POLDEK_INSTALL="$ORIG_POLDEK_INSTALL"
-+}
-+
-+# glob pattern should install all matching arches
-+testInstallGlobMultilibAllArches() {
-+ ORIG_POLDEK_INSTALL="$POLDEK_INSTALL"
-+ POLDEK_INSTALL="$POLDEK_INSTALL -Omultilib=1"
-+
-+ msgn "Preparing repositories..."
-+ build a 1-1 -a x86_64 -f "/hello.x86_64"
-+ build a 1-1 -a i686 -f "/hello.i686"
-+
-+ msgn "Glob should install all matching arches"
-+ try_install_with_ask_allowed "a*" "a-1-1.x86_64,a-1-1.i686"
-+
-+ POLDEK_INSTALL="$ORIG_POLDEK_INSTALL"
-+}
-
- . ./sh/lib/shunit2
---
-2.53.0
-
diff --git a/poldek-nocolor-cmp.patch b/poldek-nocolor-cmp.patch
deleted file mode 100644
index abfa538..0000000
--- a/poldek-nocolor-cmp.patch
+++ /dev/null
@@ -1,56 +0,0 @@
- fix: do not treat uncolored packages from different architectures as interchangeable
-
- -devel packages typically have HEADERCOLOR=0 (no ELF binaries, only
- headers, symlinks, pkg-config and libtool files). pkg_is_colored_like()
- was returning 1 ("same kind") whenever at least one package had color 0,
- causing poldek to incorrectly obsolete e.g. xz-devel.x32 when installing
- xz-devel.x86_64, even though they install to different paths
- (/usr/libx32 vs /usr/lib64) and are not interchangeable.
-
- The uncolored fallback was changed to a blanket "return 1" in commit
- dad87fe8 (LP#299685, 2010). The earlier commit 1fd8aa21 (2008) disabled
- arch-based fallback with the comment "rpm relies on colors only".
-
- Regardless of what older RPM versions did, RPM 6.0.1's checkAdded() and
- rpmtsTeIterator() (lib/depends.cc) use arch string matching as a second
- layer alongside colors in multilib transactions.
-
- Fix pkg_is_colored_like() to fall back to arch comparison when at least
- one package is uncolored, and remove the redundant "any uncolored"
- bypass in obs_filter() which was a second path for the same bug.
-
-diff --git a/install3/obsoletes.c b/install3/obsoletes.c
-index c70c04da..d137aeac 100644
---- a/install3/obsoletes.c
-+++ b/install3/obsoletes.c
-@@ -132,10 +132,6 @@ int obs_filter(struct pkgdb *db, const struct pm_dbrec *dbrec, void *apkg)
- if (pkg_is_colored_like(&dbpkg, pkg))
- return 1;
-
-- /* any uncolored -> rpm allows upgrade */
-- if (dbpkg.color == 0 || pkg->color == 0)
-- return 1;
--
- return 0;
- }
-
-diff --git a/pkgcmp.c b/pkgcmp.c
-index aa8af162..e75ddf5b 100644
---- a/pkgcmp.c
-+++ b/pkgcmp.c
-@@ -80,8 +80,13 @@ int pkg_is_colored_like(const struct pkg *candidate, const struct pkg *pkg)
- if (pkg->color && candidate->color)
- return (pkg->color & candidate->color) > 0;
-
-- /* either new or old package contains no binary files, let it happen */
-- return 1;
-+ /* When at least one package has no ELF binaries (color 0), fall back
-+ to arch comparison. This prevents treating uncolored packages from
-+ different architectures (e.g. x32 vs x86_64 -devel packages) as
-+ interchangeable. RPM 6.0.1 itself uses arch string matching as a
-+ second layer alongside colors (see checkAdded() and
-+ rpmtsTeIterator() in rpm's lib/depends.cc). */
-+ return pkg_cmp_same_arch(candidate, pkg);
- }
-
- /* ret : 1 if pkg is cappable to upgrade arch<=>arch, arch<=>noarch */
diff --git a/poldek-scoring-evr.patch b/poldek-scoring-evr.patch
deleted file mode 100644
index f83d6ad..0000000
--- a/poldek-scoring-evr.patch
+++ /dev/null
@@ -1,101 +0,0 @@
-commit 2d65d2e1063e0f9c7d2196eb9170cd3426192462
-Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
-Date: Wed Apr 15 21:29:46 2026 +0200
-
- Break ties between equivalent providers by preferring highest EVR
-
- When multiple packages provide the same capability and all score
- equally on every signal (satisfiability, name-prefix match, arch,
- conflicts, upgrade), the winner was determined by alphabetical sort
- order of the package name. This systematically picked the oldest
- available version — e.g. php4-program over php85-program when 23
- packages provide /usr/bin/php.
-
- Add an EVR (epoch-version-release) comparison as a tiebreaker in
- do_select_best_pkg(). When two candidates have identical scores,
- the one with the higher EVR wins. This only fires when all other
- scoring signals are equal — it never overrides a score-based
- preference. Version constraints from the requirement are enforced
- before candidates reach scoring, so the tiebreaker can only choose
- among packages that already satisfy the requirement.
-
- Apply the same tiebreaker in prepare_icap() for the --caplookup
- path: when no provider is installed, pick highest EVR instead of
- alphabetically first.
-
- Tested on 133 PHP packages and 49 non-PHP packages against a
- builder-like chroot (base system, no PHP installed):
-
- PHP: Before the fix, 44 out of 133 packages pulled in a mix of
- PHP versions (e.g. php4 + php52 + php53 + php74 simultaneously).
- After the fix, all 133 resolve to a single consistent version
- (php85), except 2 packages that depend on extensions only available
- for old PHP (php-mnogosearch, php-xcache).
-
- Java: openjdk8-jre (epoch=1) now selected over icedtea8-jre
- (no epoch) for ant and maven.
-
- Init system: systemd-init (epoch=1) now selected over SysVinit
- (no epoch) for packages requiring an init daemon.
-
-diff --git a/install3/misc.c b/install3/misc.c
-index b1940972..c6b7c0cb 100644
---- a/install3/misc.c
-+++ b/install3/misc.c
-@@ -370,6 +370,22 @@ static int do_select_best_pkg(int indent, struct i3ctx *ictx,
- if (sc->score > best_score) {
- best_score = sc->score;
- i_best = i;
-+ } else if (sc->score == best_score) {
-+ /* EVR tiebreaker: when scores are tied, prefer the
-+ candidate with the highest epoch-version-release.
-+ Without this, the winner is the first candidate in
-+ alphabetical name order, which systematically picks
-+ the oldest version (e.g. php4-program over
-+ php85-program when 23 packages provide /usr/bin/php).
-+ This is safe because version constraints are enforced
-+ before candidates reach scoring — only packages that
-+ satisfy the requirement are considered here.
-+ See also prepare_icap() for the --caplookup path. */
-+ struct pkg *pbest = n_array_nth(candidates, i_best);
-+ struct pkg *pcand = n_array_nth(candidates, i);
-+ if (pkg_cmp_evr(pcand, pbest) > 0) {
-+ i_best = i;
-+ }
- }
-
- if (sc->satscore > best_satscore) {
-diff --git a/install3/preinstall.c b/install3/preinstall.c
-index b237c1b4..1e7e5934 100644
---- a/install3/preinstall.c
-+++ b/install3/preinstall.c
-@@ -90,8 +90,16 @@ int prepare_icap(struct poldek_ts *ts, const char *capname, tn_array *pkgs)
- }
- }
-
-- if (pkg == NULL)
-+ if (pkg == NULL) {
-+ /* EVR tiebreaker: pick highest EVR instead of
-+ alphabetically first; see also do_select_best_pkg() */
- pkg = n_array_nth(pkgs, 0);
-+ for (i = 1; i < n_array_size(pkgs); i++) {
-+ struct pkg *p = n_array_nth(pkgs, i);
-+ if (pkg_cmp_evr(p, pkg) > 0)
-+ pkg = p;
-+ }
-+ }
-
- pkg_hand_mark(ts->pms, pkg);
- return 1;
-diff --git a/pkgcmp.c b/pkgcmp.c
-index e75ddf5b..05fce886 100644
---- a/pkgcmp.c
-+++ b/pkgcmp.c
-@@ -170,7 +170,6 @@ int pkg_cmp_evr(const struct pkg *p1, const struct pkg *p2)
- return rc;
- }
-
--
- int pkg_cmp_name_evr(const struct pkg *p1, const struct pkg *p2)
- {
- register int rc;
diff --git a/poldek-search-i-opt.patch b/poldek-search-i-opt.patch
deleted file mode 100644
index c127e45..0000000
--- a/poldek-search-i-opt.patch
+++ /dev/null
@@ -1,138 +0,0 @@
-diff --git a/cli/search.c b/cli/search.c
-index 4d6f06a..6db2ebc 100644
---- a/cli/search.c
-+++ b/cli/search.c
-@@ -61,6 +61,7 @@ static int pcre_established = 0;
- struct pattern {
- int type;
- char *regexp;
-+ int ignore_case;
- int fnmatch_flags;
- unsigned pcre_flags;
- pcre *pcre;
-@@ -71,6 +72,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state);
- static int search(struct cmdctx *cmdctx);
-
- #define OPT_PATTERN_PCRE (1 << 10)
-+#define OPT_PATTERN_ICASE (1 << 11)
-
- #define OPT_SEARCH_CAP (1 << 0)
- #define OPT_SEARCH_REQ (1 << 1)
-@@ -95,7 +97,7 @@ static int search(struct cmdctx *cmdctx);
- OPT_SEARCH_CHANGELOG)
-
-
--#define OPT_NO_SEARCHSW OPT_PATTERN_PCRE
-+#define OPT_NO_SEARCHSW (OPT_PATTERN_PCRE | OPT_PATTERN_ICASE)
-
- static struct argp_option options[] = {
- { "provides", 'p', 0, 0, N_("Search capabilities"), 1},
-@@ -106,6 +108,7 @@ static struct argp_option options[] = {
- { "summary", 's', 0, 0, N_("Search summaries, urls and license"), 1},
- { "description", 'd', 0, 0, N_("Search descriptions"), 1},
- { "group", 'g', 0, 0, N_("Search groups"), 1 },
-+ { "ignore-case", 'i', 0, 0, N_("Ignore case when matching"), 1},
- { "files", 'f', 0, 0, N_("Search file list"), 1},
- { NULL, 'l', 0, OPTION_ALIAS, 0, 1},
- { "changelog", 'L', 0, 0, N_("Search changelogs"), 1},
-@@ -124,7 +127,10 @@ struct poclidek_cmd command_search = {
- options, parse_opt,
- NULL, search,
- NULL, NULL,
-- N_("With --perlre pattern must be supplied as:\n"
-+ N_("Patterns are matched case-sensitively by default.\n"
-+ "Use -i, --ignore-case to ignore case.\n"
-+ "\n"
-+ "With --perlre pattern must be supplied as:\n"
- " <delimiter>perl-regexp<delimiter>[imsx]\n"
- " For example to find the packages containing foo.bar do:\n"
- " search --perlre /foo\\.bar/\n"
-@@ -204,10 +210,15 @@ static struct pattern *build_pattern(struct cmdctx *cmdctx, char *arg)
- pt->type = PATTERN_FMASK;
-
- pt->regexp = n_strdup(regexp);
-+ pt->ignore_case = ((cmdctx->_flags & OPT_PATTERN_ICASE) != 0);
- pt->fnmatch_flags = 0;
- pt->pcre_flags = flags;
- pt->pcre = NULL;
- pt->pcre_extra = NULL;
-+
-+ if (pt->ignore_case && pt->type == PATTERN_PCRE)
-+ pt->pcre_flags |= PCRE_CASELESS;
-+
- return pt;
- }
-
-@@ -237,6 +248,10 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
- cmdctx->_flags |= OPT_SEARCH_GROUP;
- break;
-
-+ case 'i':
-+ cmdctx->_flags |= OPT_PATTERN_ICASE;
-+ break;
-+
- case 'o':
- cmdctx->_flags |= OPT_SEARCH_OBSL;
- break;
-@@ -322,7 +337,13 @@ int pattern_compile(struct pattern *pt, int ntimes)
- n_assert(pt->pcre_extra == NULL);
-
- #ifdef FNM_CASEFOLD
-- pt->fnmatch_flags |= FNM_CASEFOLD;
-+ if (pt->ignore_case)
-+ pt->fnmatch_flags |= FNM_CASEFOLD;
-+#else
-+ if (pt->ignore_case && pt->type == PATTERN_FMASK) {
-+ logn(LOGERR, _("search: case-insensitive glob matching is not supported on this platform"));
-+ return 0;
-+ }
- #endif
-
- if (pt->type != PATTERN_PCRE)
-diff --git a/doc/manual.xml b/doc/manual.xml
-index 9d8923c..6c4fcdc 100644
---- a/doc/manual.xml
-+++ b/doc/manual.xml
-@@ -846,7 +846,9 @@ Syntax of command is:
- search [OPTION...] PATTERN [PACKAGE...]
- </screen>
- Where <emphasis>PATTERN</emphasis> is a glob or, with <option>--perlre</option>,
--Perl regular expression.
-+Perl regular expression. Matching is case-sensitive by default; use
-+<option>-i</option>, <option>--ignore-case</option> for case-insensitive
-+matching.
-
- For instance to find the packages that contains <filename>/usr/sbin/ab</filename> file:
- <screen>
-diff --git a/doc/poldek.1 b/doc/poldek.1
-index fd7982e..273e100 100644
---- a/doc/poldek.1
-+++ b/doc/poldek.1
-@@ -51,6 +51,12 @@ is a full\-featured packages management utility, basically designed to work with
- This manual page covers command line option reference, the full documentation for poldek is available as a Texinfo manual\&. Do
- \fBinfo poldek\fR
- to get it\&.
-+.PP
-+In shell mode, the
-+\fBsearch\fR
-+command matches case\-sensitively by default; use
-+\fBsearch \-i\fR
-+for case\-insensitive matching\&.
- .SH "OPTIONS REFERENCE"
- .SS "Repository index creation"
- .PP
-diff --git a/doc/poldek.1.xml b/doc/poldek.1.xml
-index e1a923a..a7eeff1 100644
---- a/doc/poldek.1.xml
-+++ b/doc/poldek.1.xml
-@@ -44,6 +44,10 @@ tasks like installation, upgrading or removal.
- <para>
- This manual page covers command line option reference, the full documentation
- for poldek is available as a Texinfo manual. Do <command>info poldek</command> to get it.
-+</para>
-+<para>
-+In shell mode, the <command>search</command> command matches case-sensitively
-+by default; use <command>search -i</command> for case-insensitive matching.
- </para>
- </refsect1>
-
diff --git a/poldek-url-keep-port.patch b/poldek-url-keep-port.patch
deleted file mode 100644
index 7cdf0ae..0000000
--- a/poldek-url-keep-port.patch
+++ /dev/null
@@ -1,50 +0,0 @@
- fix: keep an explicit port in redirect targets
-
- Two places dropped it:
-
- - vf_request_new() reassembled req->url without the port, so everything
- that re-parses that string - the redirect retries in vf_stat() and
- vfile__vf_fetch(), and the absolute URI sent to a proxy - silently
- went to the protocol default port.
- - do_vfn() resolves a relative Location against the request itself and
- built that URL from req->host alone, so a repository on a
- non-default port reconnected to 80 or 443.
-
-diff --git a/vfile/vfreq.c b/vfile/vfreq.c
---- a/vfile/vfreq.c
-+++ b/vfile/vfreq.c
-@@ -278,8 +278,12 @@
- else
- req->uri = n_strdupl(tmp, len);
-
-- len = n_snprintf(tmp, sizeof(tmp), "%s://%s%s", rreq.proto, rreq.host,
-- req->uri);
-+ if (rreq.port > 0)
-+ len = n_snprintf(tmp, sizeof(tmp), "%s://%s:%d%s", rreq.proto, rreq.host,
-+ rreq.port, req->uri);
-+ else
-+ len = n_snprintf(tmp, sizeof(tmp), "%s://%s%s", rreq.proto, rreq.host,
-+ req->uri);
- req->url = n_strdupl(tmp, len);
- req->port = rreq.port;
-
-diff --git a/vfile/vfffmod.c b/vfile/vfffmod.c
---- a/vfile/vfffmod.c
-+++ b/vfile/vfffmod.c
-@@ -280,8 +280,14 @@
- n_assert(cn->proto == VCN_PROTO_HTTP || cn->proto == VCN_PROTO_HTTPS);
-
- if (*vreq.redirected_to == '/') {
-- snprintf(topath, sizeof(topath), "http%s://%s%s", cn->proto == VCN_PROTO_HTTPS ? "s" : "" , req->host,
-- vreq.redirected_to);
-+ char portstr[16] = "";
-+
-+ if (req->port > 0)
-+ snprintf(portstr, sizeof(portstr), ":%d", req->port);
-+
-+ snprintf(topath, sizeof(topath), "http%s://%s%s%s",
-+ cn->proto == VCN_PROTO_HTTPS ? "s" : "", req->host,
-+ portstr, vreq.redirected_to);
- topathp = topath;
- } else if (strncmp(vreq.redirected_to, "http://", 7) != 0)
- foreign_proto = 1;
diff --git a/poldek-vcn-pool-reuse.patch b/poldek-vcn-pool-reuse.patch
deleted file mode 100644
index 4d67b0f..0000000
--- a/poldek-vcn-pool-reuse.patch
+++ /dev/null
@@ -1,53 +0,0 @@
- fix: do not hand out unusable connections from the pool
-
- Three ways a connection nobody can talk on gets used again:
-
- - vcn_is_alive() short-circuits on a timestamp younger than
- VCN_ALIVE_TTL without looking at cn->state, so a closed or dead
- connection is reported alive.
- - vcn_pool_vacuum() calls n_list_remove_ex() once, and on a
- TN_LIST_UNIQ list that drops at most one match, so dead connections
- accumulate in the pool.
- - vftpcn_retr() leaves the control connection ALIVE after an aborted
- data transfer even though the server's reply to the aborted RETR is
- still unread; reusing it desynchronizes every following response, so
- SIZE reads the reply to RETR, MDTM reads the reply to SIZE and so on.
-
-diff --git a/vfile/vfff/ftp.c b/vfile/vfff/ftp.c
---- a/vfile/vfff/ftp.c
-+++ b/vfile/vfff/ftp.c
-@@ -750,6 +750,7 @@
-
- if (!vfff_transfer_file(cn, req, total_size)) {
- cn->sockfd = tmp_sockfd;
-+ cn->state = VCN_DEAD; /* reply to the aborted RETR stays unread */
- goto l_err;
- }
-
-diff --git a/vfile/vfff/vfff.c b/vfile/vfff/vfff.c
---- a/vfile/vfff/vfff.c
-+++ b/vfile/vfff/vfff.c
-@@ -342,6 +342,9 @@
- {
- vfff_errno = 0;
-
-+ if (cn->state != VCN_ALIVE)
-+ return 0;
-+
- if (cn->ts_is_alive > 0) {
- time_t ts = time(0);
-
-diff --git a/vfile/vfffmod.c b/vfile/vfffmod.c
---- a/vfile/vfffmod.c
-+++ b/vfile/vfffmod.c
-@@ -88,7 +88,9 @@
-
- void vcn_pool_vacuum(void)
- {
-- n_list_remove_ex(vcn_pool, NULL, toremove_cn_fakecmp);
-+ /* pool is TN_LIST_UNIQ, so one call drops at most one dead connection */
-+ while (n_list_remove_ex(vcn_pool, NULL, toremove_cn_fakecmp) > 0)
-+ ;
- }
-
- static struct vcn *vcn_pool_do_connect(struct vf_request *req)
diff --git a/poldek-vfile-net-tests.patch b/poldek-vfile-net-tests.patch
deleted file mode 100644
index 9554823..0000000
--- a/poldek-vfile-net-tests.patch
+++ /dev/null
@@ -1,603 +0,0 @@
- tests: network fault tests for the vfile fixes
-
- Covers the crashes and the connection reuse bugs fixed by the patches
- above: an unreadable PASV reply, a 227 with no address, an aborted data
- transfer leaving the control connection desynchronized, a stat over a
- failing protocol-changing redirect, and absolute and relative redirect
- retries that have to keep the port.
-
- net_servers.py is a minimal FTP server plus an HTTP redirector, both on
- loopback, with the faults selected by the requested path. test_vfnet is
- built by "make check" but deliberately not run by it - it needs those
- servers, so it is wired to its own target and started with "make check-net".
-
-diff --git a/vfile/tests/Makefile.am b/vfile/tests/Makefile.am
---- a/vfile/tests/Makefile.am
-+++ b/vfile/tests/Makefile.am
-@@ -1,10 +1,15 @@
- CPPFLAGS = @CPPFLAGS@ @CHECK_CFLAGS@
- LDADD = @CHECK_LIBS@ $(top_builddir)/vfile/libvfile.la
-
--check_PROGRAMS = test_vfile test_vopen3
-+check_PROGRAMS = test_vfile test_vopen3 test_vfnet
-
--TESTS = $(check_PROGRAMS)
--EXTRA_DIST = test.h
-+TESTS = test_vfile test_vopen3
-+EXTRA_DIST = test.h net_servers.py run_net_tests.sh
-+
-+# test_vfnet needs the helper servers, so it is not part of "make check";
-+# run it with: make check-net
-+check-net: test_vfnet$(EXEEXT)
-+ $(SHELL) $(srcdir)/run_net_tests.sh ./test_vfnet$(EXEEXT)
-
- clean-local:
- -rm -f *.tmp core *.o *.bak *~ *% *\# TAGS gmon.out \#*\# dupa*
-diff --git a/vfile/tests/net_servers.py b/vfile/tests/net_servers.py
-new file mode 100644
---- /dev/null
-+++ b/vfile/tests/net_servers.py
-@@ -0,0 +1,250 @@
-+#!/usr/bin/env python3
-+"""
-+Network fault-injection servers for the vfile tests.
-+
-+Runs a minimal FTP server and a minimal HTTP redirector on loopback. Both
-+are only good enough to drive vfile; the point is the faults they can inject.
-+
-+FTP faults are selected by the requested path (the server learns it from
-+SIZE/MDTM, which poldek sends before PASV):
-+ /pasv-drop/<name> - abort the control connection when PASV arrives
-+ /pasv-nodigits/<name> - answer PASV with a 227 carrying no address at all
-+ /data-reset/<name> - reset the data connection during RETR and leave
-+ the reply to the aborted transfer unread
-+
-+HTTP serves nothing but redirects:
-+ /to-ftp/<name> - 302 to ftp://127.0.0.1:<ftp port>/<name>
-+ /to-dead-https/<name> - 302 to https://127.0.0.1:1/<name>
-+ /rel-to-ftp/<name> - 302 to the relative path /to-ftp/<name>
-+"""
-+
-+import argparse
-+import os
-+import socket
-+import struct
-+import sys
-+import threading
-+import time
-+
-+
-+def log(verbose, msg):
-+ if verbose:
-+ print(msg, file=sys.stderr, flush=True)
-+
-+
-+def listen(port):
-+ sock = socket.socket()
-+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-+ sock.bind(('127.0.0.1', port))
-+ sock.listen(16)
-+ return sock
-+
-+
-+def reset(sock):
-+ """Close so that the peer sees RST instead of an orderly EOF"""
-+ try:
-+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
-+ struct.pack('ii', 1, 0))
-+ sock.close()
-+ except OSError:
-+ pass
-+
-+
-+class FTPSession:
-+ def __init__(self, sock, data_dir, verbose):
-+ self.sock = sock
-+ self.data_dir = data_dir
-+ self.verbose = verbose
-+ self.stream = sock.makefile('rwb', buffering=0)
-+ self.datasock = None
-+ self.path = ''
-+
-+ def log(self, msg):
-+ log(self.verbose, "ftpd: %s" % msg)
-+
-+ def send(self, line):
-+ self.log("> %s" % line.strip())
-+ self.stream.write(line.encode())
-+
-+ def abort(self):
-+ """makefile() holds a reference, so the socket only really closes
-+ once the stream is closed too"""
-+ self.stream.close()
-+ reset(self.sock)
-+
-+ def localpath(self):
-+ return os.path.join(self.data_dir, os.path.basename(self.path))
-+
-+ def open_datasock(self):
-+ self.datasock = listen(0)
-+ return self.datasock.getsockname()[1]
-+
-+ def serve(self):
-+ try:
-+ self.run()
-+ except OSError: # client went away
-+ pass
-+
-+ def run(self):
-+ self.send("220 vfile test ftpd\r\n")
-+ while True:
-+ line = self.stream.readline()
-+ if not line:
-+ break
-+ line = line.decode(errors='replace').strip()
-+ self.log("< %s" % line)
-+ parts = line.split(None, 1)
-+ if not parts:
-+ continue
-+ cmd = parts[0].upper()
-+ arg = parts[1] if len(parts) > 1 else ''
-+
-+ if cmd in ('SIZE', 'MDTM', 'RETR'):
-+ self.path = arg
-+
-+ if cmd in ('PASV', 'EPSV') and 'pasv-drop' in self.path:
-+ self.log("!! dropping control connection at %s" % cmd)
-+ self.abort()
-+ return
-+
-+ if cmd in ('PASV', 'EPSV') and 'pasv-nodigits' in self.path:
-+ self.send("227 Entering Passive Mode\r\n")
-+ continue
-+
-+ if cmd == 'USER':
-+ self.send("331 password required\r\n")
-+ elif cmd == 'PASS':
-+ self.send("230 logged in\r\n")
-+ elif cmd in ('TYPE', 'NOOP'):
-+ self.send("200 ok\r\n")
-+ elif cmd == 'SIZE':
-+ if os.path.isfile(self.localpath()):
-+ self.send("213 %d\r\n" % os.path.getsize(self.localpath()))
-+ else:
-+ self.send("550 no such file\r\n")
-+ elif cmd == 'MDTM':
-+ if os.path.isfile(self.localpath()):
-+ ts = time.gmtime(os.path.getmtime(self.localpath()))
-+ self.send("213 %s\r\n" % time.strftime("%Y%m%d%H%M%S", ts))
-+ else:
-+ self.send("550 no such file\r\n")
-+ elif cmd == 'PASV':
-+ port = self.open_datasock()
-+ self.send("227 Entering Passive Mode (127,0,0,1,%d,%d)\r\n"
-+ % (port >> 8, port & 0xff))
-+ elif cmd == 'EPSV':
-+ self.send("229 Entering Extended Passive Mode (|||%d|)\r\n"
-+ % self.open_datasock())
-+ elif cmd == 'REST':
-+ self.send("350 ok\r\n")
-+ elif cmd == 'RETR':
-+ self.retr()
-+ elif cmd == 'QUIT':
-+ self.send("221 bye\r\n")
-+ break
-+ else:
-+ self.send("500 unknown command\r\n")
-+
-+ self.sock.close()
-+
-+ def retr(self):
-+ if not os.path.isfile(self.localpath()) or self.datasock is None:
-+ self.send("550 no such file\r\n")
-+ return
-+
-+ size = os.path.getsize(self.localpath())
-+ self.send("150 Opening BINARY mode data connection (%d bytes)\r\n" % size)
-+ conn, _ = self.datasock.accept()
-+
-+ if 'data-reset' in self.path:
-+ self.log("!! resetting data connection")
-+ reset(conn)
-+ self.datasock.close()
-+ self.datasock = None
-+ self.send("426 Transfer aborted\r\n")
-+ return
-+
-+ with open(self.localpath(), 'rb') as f:
-+ conn.sendall(f.read())
-+ conn.close()
-+ self.datasock.close()
-+ self.datasock = None
-+ self.send("226 Transfer complete\r\n")
-+
-+
-+def http_session(sock, ftp_port, verbose):
-+ try:
-+ req = b""
-+ while b"\r\n\r\n" not in req:
-+ data = sock.recv(4096)
-+ if not data:
-+ return
-+ req += data
-+ line = req.split(b"\r\n")[0].decode(errors='replace')
-+ log(verbose, "httpd: < %s" % line)
-+ path = line.split()[1]
-+ name = os.path.basename(path)
-+
-+ if path.startswith("/to-ftp/"):
-+ to = "ftp://127.0.0.1:%d/%s" % (ftp_port, name)
-+ elif path.startswith("/rel-to-ftp/"):
-+ to = "/to-ftp/%s" % name
-+ elif path.startswith("/to-dead-https/"):
-+ to = "https://127.0.0.1:1/%s" % name
-+ else:
-+ sock.sendall(b"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n"
-+ b"Connection: close\r\n\r\n")
-+ return
-+
-+ log(verbose, "httpd: > 302 %s" % to)
-+ sock.sendall(("HTTP/1.1 302 Found\r\nLocation: %s\r\nContent-Length: 0\r\n"
-+ "Connection: close\r\n\r\n" % to).encode())
-+ except OSError:
-+ pass
-+ finally:
-+ try:
-+ sock.close()
-+ except OSError:
-+ pass
-+
-+
-+def main():
-+ parser = argparse.ArgumentParser(description='vfile network test servers')
-+ parser.add_argument('--ftp-port', type=int, default=0)
-+ parser.add_argument('--http-port', type=int, default=0)
-+ parser.add_argument('--data-dir', default='./data')
-+ parser.add_argument('--write-ports',
-+ help='write "<ftp port> <http port>" to this file')
-+ parser.add_argument('--verbose', '-v', action='store_true')
-+ args = parser.parse_args()
-+
-+ verbose = args.verbose or bool(os.environ.get('VERBOSE'))
-+
-+ ftp_srv = listen(args.ftp_port)
-+ http_srv = listen(args.http_port)
-+ ftp_port = ftp_srv.getsockname()[1]
-+ http_port = http_srv.getsockname()[1]
-+
-+ if args.write_ports:
-+ with open(args.write_ports, 'w') as f:
-+ f.write("%d %d" % (ftp_port, http_port))
-+
-+ print("test servers on ftp://127.0.0.1:%d and http://127.0.0.1:%d"
-+ % (ftp_port, http_port), flush=True)
-+
-+ def accept_ftp():
-+ while True:
-+ conn, _ = ftp_srv.accept()
-+ session = FTPSession(conn, args.data_dir, verbose)
-+ threading.Thread(target=session.serve, daemon=True).start()
-+
-+ threading.Thread(target=accept_ftp, daemon=True).start()
-+
-+ while True:
-+ conn, _ = http_srv.accept()
-+ threading.Thread(target=http_session,
-+ args=(conn, ftp_port, verbose), daemon=True).start()
-+
-+
-+if __name__ == '__main__':
-+ main()
-diff --git a/vfile/tests/test_vfnet.c b/vfile/tests/test_vfnet.c
-new file mode 100644
---- /dev/null
-+++ b/vfile/tests/test_vfnet.c
-@@ -0,0 +1,229 @@
-+/*
-+ * Network fault tests for vfile
-+ * Regression coverage for crashes and connection reuse on broken links
-+ */
-+
-+#include <stdio.h>
-+#include <stdlib.h>
-+#include <string.h>
-+#include <time.h>
-+#include <unistd.h>
-+#include <sys/types.h>
-+#include <sys/stat.h>
-+#include <fcntl.h>
-+#include <dirent.h>
-+
-+#include "../vfile.h"
-+#include "test.h"
-+#include <trurl/nstr.h>
-+
-+static char server_host[256] = "127.0.0.1";
-+static int ftp_port = 0;
-+static int http_port = 0;
-+static char tmpdir[PATH_MAX];
-+
-+#define TEST_FILE_SMALL "small.txt"
-+
-+static int vfile_initialized = 0;
-+
-+static void net_setup(void)
-+{
-+ static int verbose = 0;
-+
-+ if (getenv("VERBOSE"))
-+ verbose = 2;
-+
-+ if (!vfile_initialized) {
-+ vfile_configure(VFILE_CONF_VERBOSE, &verbose);
-+ vfile_configure(VFILE_CONF_CACHEDIR, "/tmp/vfile_test_cache");
-+ vfile_configure(VFILE_CONF_STUBBORN_RETR, 0); /* disable retrying */
-+ vfile_setup();
-+ vfile_initialized = 1;
-+ }
-+
-+ const char *host = getenv("TEST_SERVER_HOST");
-+ const char *fport = getenv("TEST_FTP_PORT");
-+ const char *hport = getenv("TEST_HTTP_PORT");
-+
-+ if (host) n_strncpy(server_host, host, sizeof(server_host));
-+ if (fport) ftp_port = atoi(fport);
-+ if (hport) http_port = atoi(hport);
-+
-+ snprintf(tmpdir, sizeof(tmpdir), "/tmp/vfile_nettest_%d_%ld", getpid(),
-+ (long)time(NULL));
-+ mkdir(tmpdir, 0755);
-+}
-+
-+static void net_teardown(void)
-+{
-+ DIR *dir;
-+ struct dirent *ent;
-+ char path[PATH_MAX];
-+
-+ /* successful fetches leave the file and its lock behind */
-+ if ((dir = opendir(tmpdir)) != NULL) {
-+ while ((ent = readdir(dir)) != NULL) {
-+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
-+ continue;
-+ if ((size_t)snprintf(path, sizeof(path), "%s/%s", tmpdir,
-+ ent->d_name) < sizeof(path))
-+ unlink(path);
-+ }
-+ closedir(dir);
-+ }
-+
-+ rmdir(tmpdir);
-+}
-+
-+static int fetch_ftp(const char *path)
-+{
-+ char url[512];
-+
-+ snprintf(url, sizeof(url), "ftp://%s:%d/%s", server_host, ftp_port, path);
-+ return vf_fetch(url, tmpdir, 0, NULL, NULL);
-+}
-+
-+static int have_file(const char *name)
-+{
-+ char dest[PATH_MAX];
-+
-+ if ((size_t)snprintf(dest, sizeof(dest), "%s/%s", tmpdir, name) >= sizeof(dest))
-+ ck_abort_msg("path truncated");
-+ return access(dest, F_OK) == 0;
-+}
-+
-+static void drop_file(const char *name)
-+{
-+ char dest[PATH_MAX];
-+
-+ if ((size_t)snprintf(dest, sizeof(dest), "%s/%s", tmpdir, name) >= sizeof(dest))
-+ ck_abort_msg("path truncated");
-+ unlink(dest);
-+}
-+
-+START_TEST(test_ftp_get_small)
-+{
-+ fail_if(ftp_port == 0);
-+
-+ fail_if(fetch_ftp(TEST_FILE_SMALL) == 0);
-+ fail_if(!have_file(TEST_FILE_SMALL));
-+}
-+END_TEST
-+
-+/* server drops the control connection while poldek waits for the PASV reply;
-+ used to segfault on vfff_set_err() with the unread response as format */
-+START_TEST(test_ftp_pasv_control_drop)
-+{
-+ fail_if(ftp_port == 0);
-+
-+ fail_if(fetch_ftp("pasv-drop/" TEST_FILE_SMALL) != 0);
-+}
-+END_TEST
-+
-+/* 227 reply with no address in it; the scan for the first digit used to run
-+ off the end of the response buffer */
-+START_TEST(test_ftp_pasv_no_digits)
-+{
-+ fail_if(ftp_port == 0);
-+
-+ fail_if(fetch_ftp("pasv-nodigits/" TEST_FILE_SMALL) != 0);
-+}
-+END_TEST
-+
-+/* an aborted data transfer leaves the reply to RETR unread, so the control
-+ connection must not be handed out again from the pool */
-+START_TEST(test_ftp_reuse_after_aborted_transfer)
-+{
-+ fail_if(ftp_port == 0);
-+
-+ /* first transfer succeeds and stamps the connection as alive */
-+ fail_if(fetch_ftp(TEST_FILE_SMALL) == 0);
-+ drop_file(TEST_FILE_SMALL);
-+
-+ fail_if(fetch_ftp("data-reset/" TEST_FILE_SMALL) != 0);
-+
-+ /* within VCN_ALIVE_TTL, so the pool would skip its NOOP probe */
-+ fail_if(fetch_ftp(TEST_FILE_SMALL) == 0);
-+ fail_if(!have_file(TEST_FILE_SMALL));
-+}
-+END_TEST
-+
-+/* stat over a redirect that switches protocol and then fails; the failed
-+ branch used to dereference the already freed request */
-+START_TEST(test_stat_redirect_failed)
-+{
-+ char url[512];
-+ struct vf_stat vfst;
-+
-+ fail_if(http_port == 0);
-+
-+ snprintf(url, sizeof(url), "http://%s:%d/to-dead-https/" TEST_FILE_SMALL,
-+ server_host, http_port);
-+
-+ fail_if(vf_stat(url, tmpdir, &vfst, NULL) != 0);
-+}
-+END_TEST
-+
-+/* the retried request is built from req->url, which used to lose the port */
-+START_TEST(test_redirect_keeps_port)
-+{
-+ char url[512];
-+
-+ fail_if(http_port == 0 || ftp_port == 0);
-+
-+ snprintf(url, sizeof(url), "http://%s:%d/to-ftp/" TEST_FILE_SMALL,
-+ server_host, http_port);
-+
-+ fail_if(vf_fetch(url, tmpdir, 0, NULL, NULL) == 0);
-+ fail_if(!have_file(TEST_FILE_SMALL));
-+}
-+END_TEST
-+
-+/* a relative Location is resolved against the request itself, which used to
-+ drop the port and send the retry to the protocol default */
-+START_TEST(test_relative_redirect_keeps_port)
-+{
-+ char url[512];
-+
-+ fail_if(http_port == 0 || ftp_port == 0);
-+
-+ snprintf(url, sizeof(url), "http://%s:%d/rel-to-ftp/" TEST_FILE_SMALL,
-+ server_host, http_port);
-+
-+ fail_if(vf_fetch(url, tmpdir, 0, NULL, NULL) == 0);
-+ fail_if(!have_file(TEST_FILE_SMALL));
-+}
-+END_TEST
-+
-+static Suite *vfnet_suite(void)
-+{
-+ Suite *s = suite_create("vfnet");
-+
-+ TCase *tc = tcase_create("net");
-+ tcase_add_checked_fixture(tc, net_setup, net_teardown);
-+ tcase_add_test(tc, test_ftp_get_small);
-+ tcase_add_test(tc, test_ftp_pasv_control_drop);
-+ tcase_add_test(tc, test_ftp_pasv_no_digits);
-+ tcase_add_test(tc, test_ftp_reuse_after_aborted_transfer);
-+ tcase_add_test(tc, test_stat_redirect_failed);
-+ tcase_add_test(tc, test_redirect_keeps_port);
-+ tcase_add_test(tc, test_relative_redirect_keeps_port);
-+ suite_add_tcase(s, tc);
-+
-+ return s;
-+}
-+
-+int main(int argc, char **argv)
-+{
-+ (void)argc;
-+ (void)argv;
-+ int nerr;
-+ Suite *s = vfnet_suite();
-+ SRunner *sr = srunner_create(s);
-+
-+ srunner_run_all(sr, CK_ENV);
-+ nerr = srunner_ntests_failed(sr);
-+ srunner_free(sr);
-+
-+ return (nerr == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
-+}
-diff --git a/vfile/tests/run_net_tests.sh b/vfile/tests/run_net_tests.sh
-new file mode 100644
---- /dev/null
-+++ b/vfile/tests/run_net_tests.sh
-@@ -0,0 +1,74 @@
-+#!/bin/sh
-+# Network fault test runner
-+# Starts the test servers, creates test data dynamically, runs tests, cleans up
-+
-+set -e
-+
-+VERBOSE="${VERBOSE:-}"
-+
-+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-+PYTHON="${PYTHON:-python3}"
-+
-+vprint() {
-+ if [ -n "$VERBOSE" ]; then
-+ echo "$@"
-+ fi
-+}
-+
-+TEST_DATA_DIR="$(mktemp -d)"
-+PORT_FILE="$(mktemp)"
-+
-+cleanup() {
-+ if [ -n "$SRV_PID" ]; then
-+ kill "$SRV_PID" 2>/dev/null || true
-+ wait "$SRV_PID" 2>/dev/null || true
-+ fi
-+ rm -rf "$TEST_DATA_DIR" "$PORT_FILE"
-+}
-+trap cleanup EXIT
-+
-+printf 'Hello, World!\n' > "$TEST_DATA_DIR/small.txt"
-+
-+if ! "$PYTHON" --version >/dev/null 2>&1; then
-+ echo "ERROR: Python 3 not found. Set PYTHON environment variable."
-+ exit 1
-+fi
-+
-+vprint "Starting test servers..."
-+"$PYTHON" "$SCRIPT_DIR/net_servers.py" \
-+ --data-dir "$TEST_DATA_DIR" \
-+ --write-ports "$PORT_FILE" &
-+SRV_PID="$!"
-+
-+sleep 1
-+
-+if [ ! -s "$PORT_FILE" ]; then
-+ echo "ERROR: test servers did not start" >&2
-+ exit 1
-+fi
-+FTP_PORT=$(cut -d' ' -f1 "$PORT_FILE")
-+HTTP_PORT=$(cut -d' ' -f2 "$PORT_FILE")
-+vprint "FTP: ftp://127.0.0.1:$FTP_PORT HTTP: http://127.0.0.1:$HTTP_PORT"
-+
-+# vfile honours the proxy environment, which would bypass the test servers
-+unset http_proxy https_proxy ftp_proxy
-+unset HTTP_PROXY HTTPS_PROXY FTP_PROXY
-+
-+export TEST_SERVER_HOST="127.0.0.1"
-+export TEST_FTP_PORT="$FTP_PORT"
-+export TEST_HTTP_PORT="$HTTP_PORT"
-+export TEST_DATA_DIR="$TEST_DATA_DIR"
-+
-+if [ $# -eq 0 ]; then
-+ cd "$SCRIPT_DIR"
-+ if [ -x ./test_vfnet ]; then
-+ ./test_vfnet
-+ elif [ -x ./.libs/test_vfnet ]; then
-+ ./.libs/test_vfnet
-+ else
-+ echo "ERROR: test_vfnet not found. Run 'make test_vfnet' first."
-+ exit 1
-+ fi
-+else
-+ "$@"
-+fi
diff --git a/poldek-vfstat-redirect-crash.patch b/poldek-vfstat-redirect-crash.patch
deleted file mode 100644
index 8fb11ca..0000000
--- a/poldek-vfstat-redirect-crash.patch
+++ /dev/null
@@ -1,27 +0,0 @@
- fix: do not use the request after freeing it in vf_stat()
-
- The redirect branch freed the request, set the pointer to NULL and then
- read req->url from it - a guaranteed SIGSEGV whenever a HEAD gets a
- protocol-changing redirect that afterwards fails. The arguments were
- swapped too; vf_stat() takes (url, destdir).
-
- Copy the URL out before the free, the way vfile__vf_fetch() already
- handles its own redirect retry.
-
-diff --git a/vfile/vfetch.c b/vfile/vfetch.c
---- a/vfile/vfetch.c
-+++ b/vfile/vfetch.c
-@@ -407,9 +407,12 @@
- vfstat->vf_mtime = req->st_remote_mtime > 0 ? req->st_remote_mtime : 0;
-
- } else if (req->flags & VF_REQ_INT_REDIRECTED) {
-+ char redir_url[PATH_MAX];
-+
-+ snprintf(redir_url, sizeof(redir_url), "%s", req->url);
- vf_request_free(req);
- req = NULL;
-- rc = vf_stat(destdir, req->url, vfstat, NULL);
-+ rc = vf_stat(redir_url, destdir, vfstat, NULL);
-
- } else {
- vfile_set_errno(mod->vfmod_name, req->req_errno);
================================================================
---- gitweb:
http://git.pld-linux.org/gitweb.cgi/packages/poldek.git/commitdiff/4aeb92834357540e7a0f8b9e4a72effdb289c3a0
More information about the pld-cvs-commit
mailing list