summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--config.m428
-rw-r--r--examples/librpm.php45
-rw-r--r--package.xml115
-rw-r--r--php_rpminfo.h14
-rw-r--r--rpminfo.c488
-rw-r--r--rpminfo.stub.php17
-rw-r--r--rpminfo_arginfo.h60
-rw-r--r--tests/002-rpmvercmp.phpt44
-rw-r--r--tests/003-rpminfo.phpt2
-rw-r--r--tests/005-rpminfo-full.phpt6
-rw-r--r--tests/010-rpmvercmp_error7.phpt25
-rw-r--r--tests/011-rpmvercmp_error8.phpt12
-rw-r--r--tests/012-rpmaddtag.phpt16
-rw-r--r--tests/013-rpmdbsearch-error.phpt16
-rw-r--r--tests/014-stream.phpt97
-rw-r--r--tests/bidon.rpmbin6972 -> 7599 bytes
-rw-r--r--tests/bidon.spec42
18 files changed, 834 insertions, 194 deletions
diff --git a/.gitignore b/.gitignore
index a934b10..54b9e13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# Object files
*.o
*.lo
+*.dep
# Libraries
*.lib
diff --git a/config.m4 b/config.m4
index 49f6f1d..d0e2565 100644
--- a/config.m4
+++ b/config.m4
@@ -4,29 +4,19 @@ PHP_ARG_ENABLE(rpminfo, whether to enable rpminfo support,
[ --enable-rpminfo Enable rpminfo support])
if test "$PHP_RPMINFO" != "no"; then
- dnl Write more examples of tests here...
- AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
+ PKG_CHECK_MODULES([LIBRPM], [rpm >= 4.11.3])
- AC_MSG_CHECKING(for librpm)
- if test -x "$PKG_CONFIG" && $PKG_CONFIG --exists rpm; then
- if $PKG_CONFIG rpm --atleast-version 4.11.3; then
- LIBRPM_CFLAGS=`$PKG_CONFIG rpm --cflags`
- LIBRPM_LIBDIR=`$PKG_CONFIG rpm --libs`
- LIBRPM_VERSON=`$PKG_CONFIG rpm --modversion`
- AC_MSG_RESULT(from pkgconfig: version $LIBRPM_VERSON)
- if $PKG_CONFIG rpm --atleast-version 4.13; then
- AC_DEFINE(HAVE_WEAKDEP, 1, [ Indexes on weak dependency field since RPM 4.13 ])
- fi
- else
- AC_MSG_ERROR(system librpm is too old: version 4.11.3 required)
- fi
- else
- AC_MSG_ERROR(pkg-config not found)
- fi
- PHP_EVAL_LIBLINE($LIBRPM_LIBDIR, RPMINFO_SHARED_LIBADD)
+ PHP_EVAL_LIBLINE($LIBRPM_LIBS, RPMINFO_SHARED_LIBADD)
PHP_EVAL_INCLINE($LIBRPM_CFLAGS)
+ AC_MSG_CHECKING(for rpm >= 4.13)
+ PKG_CHECK_EXISTS([rpm >= 4.13],
+ AC_DEFINE(HAVE_ARCHIVE, 1, [ Archive reader since RPM 4.13 ])
+ AC_DEFINE(HAVE_WEAKDEP, 1, [ Indexes on weak dependency field since RPM 4.13 ])
+ AC_MSG_RESULT([yes]), AC_MSG_RESULT([no])
+ )
+
PHP_SUBST(RPMINFO_SHARED_LIBADD)
PHP_NEW_EXTENSION(rpminfo, rpminfo.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
diff --git a/examples/librpm.php b/examples/librpm.php
index bed896e..6ae8b81 100644
--- a/examples/librpm.php
+++ b/examples/librpm.php
@@ -59,74 +59,47 @@ abstract class Common {
if (isset($this->info['Requirename'])) {
return $this->_dep($this->info['Requirename'], $this->info['Requireflags'], $this->info['Requireversion']);
}
- else
- {
- return NULL;
- }
+ return NULL;
case 'Conflicts':
if (isset($this->info['Conflictname'])) {
return $this->_dep($this->info['Conflictname'], $this->info['Conflictflags'], $this->info['Conflictversion']);
}
- else
- {
- return NULL;
- }
+ return NULL;
case 'Obsoletes':
if (isset($this->info['Obsoletename'])) {
return $this->_dep($this->info['Obsoletename'], $this->info['Obsoleteflags'], $this->info['Obsoleteversion']);
}
- else
- {
- return NULL;
- }
+ return NULL;
case 'Provides':
if (isset($this->info['Providename'])) {
return $this->_dep($this->info['Providename'], $this->info['Provideflags'], $this->info['Provideversion']);
}
- else
- {
- return NULL;
- }
+ return NULL;
case 'Recommends':
if (isset($this->info['Recommendname'])) {
return $this->_dep($this->info['Recommendname'], $this->info['Recommendflags'], $this->info['Recommendversion']);
}
- else
- {
- return NULL;
- }
+ return NULL;
case 'Suggests':
if (isset($this->info['Suggestname'])) {
return $this->_dep($this->info['Suggestname'], $this->info['Suggestflags'], $this->info['Suggestversion']);
}
- else
- {
- return NULL;
- }
+ return NULL;
case 'Supplements':
if (isset($this->info['Supplementname'])) {
return $this->_dep($this->info['Supplementname'], $this->info['Supplementflags'], $this->info['Supplementversion']);
}
- else
- {
- return NULL;
- }
+ return NULL;
case 'Enhances':
if (isset($this->info['Enhancename'])) {
return $this->_dep($this->info['Enhancename'], $this->info['Enhanceflags'], $this->info['Enhanceversion']);
}
- else
- {
- return NULL;
- }
+ return NULL;
case 'Files':
if (isset($this->info['Basenames'])) {
return $this->_files();
}
- else
- {
- return NULL;
- }
+ return NULL;
default:
if (isset($this->info[$name])) {
return $this->info[$name];
diff --git a/package.xml b/package.xml
index 237bd54..ff6a59e 100644
--- a/package.xml
+++ b/package.xml
@@ -13,18 +13,19 @@ Documentation: https://www.php.net/rpminfo
<email>remi@php.net</email>
<active>yes</active>
</lead>
- <date>2020-04-07</date>
+ <date>2023-11-10</date>
<version>
- <release>0.5.1dev</release>
- <api>0.5.0</api>
+ <release>1.1.1dev</release>
+ <api>1.1.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
- <license>PHP 3.01</license>
+ <license uri="https://www.php.net/license/3_01.txt" filesource="LICENSE">PHP-3.01</license>
<notes>
-- split tests for PHP 7/8
+- check open_basedir restriction
+- new function: rpmgetsymlink(string $path, string $name): ?string
</notes>
<contents>
<dir name="/">
@@ -32,6 +33,8 @@ Documentation: https://www.php.net/rpminfo
<file name="config.m4" role="src"/>
<file name="php_rpminfo.h" role="src" />
<file name="rpminfo.c" role="src"/>
+ <file name="rpminfo.stub.php" role="src"/>
+ <file name="rpminfo_arginfo.h" role="src"/>
<!-- documentation -->
<file name="CREDITS" role="doc"/>
<file name="LICENSE" role="doc"/>
@@ -51,8 +54,10 @@ Documentation: https://www.php.net/rpminfo
<file name="007-rpmdbinfo.phpt" role="test"/>
<file name="008-rpmdbsearch.phpt" role="test"/>
<file name="009-rpmdbinfo2.phpt" role="test"/>
- <file name="010-rpmvercmp_error7.phpt" role="test"/>
<file name="011-rpmvercmp_error8.phpt" role="test"/>
+ <file name="012-rpmaddtag.phpt" role="test"/>
+ <file name="013-rpmdbsearch-error.phpt" role="test"/>
+ <file name="014-stream.phpt" role="test"/>
<file name="bidon.rpm" role="test"/>
<file name="bidon-src.rpm" role="test"/>
</dir>
@@ -61,7 +66,7 @@ Documentation: https://www.php.net/rpminfo
<dependencies>
<required>
<php>
- <min>7.0.0</min>
+ <min>8.0.0</min>
</php>
<pearinstaller>
<min>1.10.0</min>
@@ -72,6 +77,102 @@ Documentation: https://www.php.net/rpminfo
<extsrcrelease/>
<changelog>
<release>
+ <date>2023-11-10</date>
+ <version>
+ <release>1.1.0</release>
+ <api>1.1.0</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license uri="https://www.php.net/license/3_01.txt" filesource="LICENSE">PHP-3.01</license>
+ <notes>
+- check open_basedir restriction
+- new function: rpmgetsymlink(string $path, string $name): ?string
+ </notes>
+ </release>
+ <release>
+ <date>2023-10-13</date>
+ <version>
+ <release>1.0.1</release>
+ <api>1.0.0</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license uri="https://www.php.net/license/3_01.txt" filesource="LICENSE">PHP-3.01</license>
+ <notes>
+- fix stack smashing on 32-bit
+- allow retrieval of hardlink content
+ </notes>
+ </release>
+ <release>
+ <date>2023-10-12</date>
+ <version>
+ <release>1.0.0</release>
+ <api>1.0.0</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license uri="https://www.php.net/license/3_01.txt" filesource="LICENSE">PHP-3.01</license>
+ <notes>
+- implement rpm stream wrapper with librpm >= 4.13
+ </notes>
+ </release>
+ <release>
+ <date>2023-09-26</date>
+ <version>
+ <release>0.7.0</release>
+ <api>0.7.0</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license uri="https://www.php.net/license/3_01.txt" filesource="LICENSE">PHP-3.01</license>
+ <notes>
+- add optional operator to rpmcmpver for consistency with version_compare
+- drop support for PHP 7
+ </notes>
+ </release>
+ <release>
+ <date>2021-06-18</date>
+ <version>
+ <release>0.6.0</release>
+ <api>0.5.0</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license uri="https://www.php.net/license/3_01.txt" filesource="LICENSE">PHP-3.01</license>
+ <notes>
+- generate arginfo from stub and add missing default values
+- raise dependency on PHP 7.2
+- raise exception on bad parameter value with PHP 8
+ </notes>
+ </release>
+ <release>
+ <date>2020-09-23</date>
+ <version>
+ <release>0.5.1</release>
+ <api>0.5.0</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license>PHP 3.01</license>
+ <notes>
+- split tests for PHP 7/8
+- improve librpm example
+ </notes>
+ </release>
+ <release>
<date>2020-04-07</date>
<version>
<release>0.5.0</release>
diff --git a/php_rpminfo.h b/php_rpminfo.h
index 2ba34ea..ddc912f 100644
--- a/php_rpminfo.h
+++ b/php_rpminfo.h
@@ -1,6 +1,6 @@
/*
+----------------------------------------------------------------------+
- | PHP Version 7 |
+ | rpminfo extension for PHP |
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
@@ -22,15 +22,9 @@
extern zend_module_entry rpminfo_module_entry;
#define phpext_rpminfo_ptr &rpminfo_module_entry
-#define PHP_RPMINFO_VERSION "0.5.1-dev"
-
-#ifdef PHP_WIN32
-# define PHP_RPMINFO_API __declspec(dllexport)
-#elif defined(__GNUC__) && __GNUC__ >= 4
-# define PHP_RPMINFO_API __attribute__ ((visibility("default")))
-#else
-# define PHP_RPMINFO_API
-#endif
+#define PHP_RPMINFO_VERSION "1.1.1-dev"
+#define PHP_RPMINFO_AUTHOR "Remi Collet"
+#define PHP_RPMINFO_LICENSE "PHP-3.01"
#ifdef ZTS
#include "TSRM.h"
diff --git a/rpminfo.c b/rpminfo.c
index 3d0489d..e3fc4f5 100644
--- a/rpminfo.c
+++ b/rpminfo.c
@@ -1,6 +1,6 @@
/*
+----------------------------------------------------------------------+
- | PHP Version 7 |
+ | rpminfo extension for PHP |
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
@@ -23,6 +23,7 @@
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
+#include "ext/standard/php_string.h"
#include <fcntl.h>
#include <rpm/rpmdb.h>
@@ -32,6 +33,21 @@
#include "php_rpminfo.h"
+#include "rpminfo_arginfo.h"
+
+#ifdef HAVE_ARCHIVE
+struct php_rpm_stream_data_t {
+ FD_t gzdi;
+ Header h;
+ rpmfiles files;
+ rpmfi fi;
+ php_stream *stream;
+};
+
+#define STREAM_DATA_FROM_STREAM() \
+ struct php_rpm_stream_data_t *self = (struct php_rpm_stream_data_t *) stream->abstract;
+#endif
+
ZEND_DECLARE_MODULE_GLOBALS(rpminfo)
static rpmts rpminfo_getts(void) {
@@ -199,16 +215,6 @@ static void rpm_header_to_zval(zval *return_value, Header h, zend_bool full)
}
}
-#if PHP_VERSION_ID < 70200
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpminfo, 0, 1, IS_ARRAY, NULL, 1)
-#else
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpminfo, 0, 1, IS_ARRAY, 1)
-#endif
- ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0)
- ZEND_ARG_TYPE_INFO(0, full, _IS_BOOL, 0)
- ZEND_ARG_TYPE_INFO(1, error, IS_STRING, 1)
-ZEND_END_ARG_INFO()
-
/* {{{ proto array rpminfo(string path [, bool full [, string &$error])
Retrieve information from a RPM file */
PHP_FUNCTION(rpminfo)
@@ -222,13 +228,16 @@ PHP_FUNCTION(rpminfo)
rpmts ts = rpminfo_getts();
if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|bz", &path, &len, &full, &error) == FAILURE) {
- return;
+ RETURN_THROWS();
}
if (error) {
ZVAL_DEREF(error);
zval_dtor(error);
ZVAL_NULL(error);
}
+ if (php_check_open_basedir(path)) {
+ RETURN_NULL();
+ }
f = Fopen(path, "r");
if (f) {
@@ -267,15 +276,6 @@ PHP_FUNCTION(rpminfo)
}
/* }}} */
-#if PHP_VERSION_ID < 70200
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmdbinfo, 0, 1, IS_ARRAY, NULL, 1)
-#else
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmdbinfo, 0, 1, IS_ARRAY, 1)
-#endif
- ZEND_ARG_TYPE_INFO(0, nevr, IS_STRING, 0)
- ZEND_ARG_TYPE_INFO(0, full, _IS_BOOL, 0)
-ZEND_END_ARG_INFO()
-
/* {{{ proto array rpmdbinfo(string nevr [, bool full])
Retrieve information from an installed RPM */
PHP_FUNCTION(rpmdbinfo)
@@ -288,13 +288,13 @@ PHP_FUNCTION(rpmdbinfo)
rpmdbMatchIterator di;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|b", &name, &len, &full) == FAILURE) {
- return;
+ RETURN_THROWS();
}
db = rpminfo_getdb();
di = rpmdbInitIterator(db, RPMDBI_LABEL, name, len);
if (!di) {
- // Not found
+ /* Not found */
RETURN_NULL();
}
@@ -319,7 +319,7 @@ static unsigned char nibble(char c) {
if (c >= 'A' && c <= 'F') {
return (c - 'A') + 10;
}
- return 0;
+ return 0;
}
static int hex2bin(const char *hex, char *bin, int len) {
@@ -363,17 +363,6 @@ static int haveIndex(zend_long tag) {
return 0;
}
-#if PHP_VERSION_ID < 70200
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmdbsearch, 0, 1, IS_ARRAY, NULL, 1)
-#else
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmdbsearch, 0, 1, IS_ARRAY, 1)
-#endif
- ZEND_ARG_TYPE_INFO(0, pattern, IS_STRING, 0)
- ZEND_ARG_TYPE_INFO(0, rpmtag, IS_LONG, 0)
- ZEND_ARG_TYPE_INFO(0, rpmmire, IS_LONG, 0)
- ZEND_ARG_TYPE_INFO(0, full, _IS_BOOL, 0)
-ZEND_END_ARG_INFO()
-
/* {{{ proto array rpmdbsearch(string pattern [, integer tag_name = RPMTAG_NAME [, integer mode = -1 [, bool full = 0]]])
Search information from installed RPMs */
PHP_FUNCTION(rpmdbsearch)
@@ -391,20 +380,32 @@ PHP_FUNCTION(rpmdbsearch)
int useIndex = 1;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|llb", &name, &len, &crit, &mode, &full) == FAILURE) {
- return;
+ RETURN_THROWS();
+ }
+ if (rpmTagGetType(crit) == RPM_NULL_TYPE) {
+ zend_argument_value_error(2, "Unkown rpmtag");
+ RETURN_THROWS();
+ }
+ if (mode != RPMMIRE_DEFAULT &&
+ mode != RPMMIRE_STRCMP &&
+ mode != RPMMIRE_REGEX &&
+ mode != RPMMIRE_GLOB &&
+ mode != -1) {
+ zend_argument_value_error(3, "Unkown rpmmire");
+ RETURN_THROWS();
}
if (crit == RPMTAG_PKGID) {
if (len != 32) {
- php_error_docref(NULL, E_WARNING, "Bad length for PKGID, 32 expected");
- RETURN_NULL();
+ zend_argument_value_error(1, "Bad length for PKGID, 32 expected");
+ RETURN_THROWS();
}
len = hex2bin(name, MD5, len);
name = MD5;
} else if (crit == RPMTAG_HDRID) {
if (len != 40) {
- php_error_docref(NULL, E_WARNING, "Bad length for HDRID, 40 expected");
- RETURN_NULL();
+ zend_argument_value_error(1, "Bad length for HDRID, 40 expected");
+ RETURN_THROWS();
}
} else if (crit == RPMTAG_INSTALLTID) {
tid = atol(name);
@@ -433,7 +434,7 @@ PHP_FUNCTION(rpmdbsearch)
}
}
if (!di) {
- // Not found
+ /* Not found */
RETURN_NULL();
}
@@ -448,15 +449,6 @@ PHP_FUNCTION(rpmdbsearch)
}
/* }}} */
-#if PHP_VERSION_ID < 70200
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmvercmp, 0, 2, IS_LONG, NULL, 0)
-#else
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmvercmp, 0, 2, IS_LONG, 0)
-#endif
- ZEND_ARG_TYPE_INFO(0, evr1, IS_STRING, 0)
- ZEND_ARG_TYPE_INFO(0, evr2, IS_STRING, 0)
-ZEND_END_ARG_INFO()
-
/* {{{ proto int rpmcmpver(string evr1, string evr2)
Compare 2 RPM EVRs (epoch:version-release) strings */
PHP_FUNCTION(rpmvercmp)
@@ -464,16 +456,17 @@ PHP_FUNCTION(rpmvercmp)
char *in_evr1, *evr1, *v1, *r1;
char *in_evr2, *evr2, *v2, *r2;
char *p, empty[] = "";
+ char *op = NULL;
long e1, e2, r;
- size_t len1, len2;
+ size_t len1, len2, oplen;
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &in_evr1, &len1, &in_evr2, &len2) == FAILURE) {
- return;
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|s", &in_evr1, &len1, &in_evr2, &len2, &op, &oplen) == FAILURE) {
+ RETURN_THROWS();
}
evr1 = estrdup(in_evr1);
evr2 = estrdup(in_evr2);
- // Epoch
+ /* Epoch */
p = strchr(evr1, ':');
if (p) {
v1 = p+1;
@@ -493,11 +486,11 @@ PHP_FUNCTION(rpmvercmp)
e2 = 0;
}
if (e1 < e2) {
- RETVAL_LONG(-1);
+ r = -1;
} else if (e1 > e2) {
- RETVAL_LONG(1);
+ r = 1;
} else {
- // Version
+ /* Version */
p = strchr(v1, '-');
if (p) {
r1 = p+1;
@@ -513,27 +506,42 @@ PHP_FUNCTION(rpmvercmp)
r2 = empty;
}
r = rpmvercmp(v1, v2);
- if (r) {
- RETVAL_LONG(r);
- } else {
- // Release
+ if (!r) {
+ /* Release*/
r = rpmvercmp(r1, r2);
- RETVAL_LONG(r);
}
}
efree(evr1);
efree(evr2);
+
+ if (!op) {
+ RETURN_LONG(r);
+ }
+
+ if (!strcmp(op, "<") || !strcmp(op, "lt")) {
+ RETURN_BOOL(r < 0);
+ }
+ if (!strcmp(op, "<=") || !strcmp(op, "le")) {
+ RETURN_BOOL(r <= 0);
+ }
+ if (!strcmp(op, ">") || !strcmp(op, "gt")) {
+ RETURN_BOOL(r > 0);
+ }
+ if (!strcmp(op, ">=") || !strcmp(op, "ge")) {
+ RETURN_BOOL(r >= 0);
+ }
+ if (!strcmp(op, "==") || !strcmp(op, "=") || !strcmp(op, "eq")) {
+ RETURN_BOOL(r == 0);
+ }
+ if (!strcmp(op, "!=") || !strcmp(op, "<>") || !strcmp(op, "ne")) {
+ RETURN_BOOL(r != 0);
+ }
+
+ zend_argument_value_error(3, "must be a valid comparison operator");
+ RETURN_THROWS();
}
/* }}} */
-#if PHP_VERSION_ID < 70200
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmaddtag, 0, 1, _IS_BOOL, NULL, 0)
-#else
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmaddtag, 0, 1, _IS_BOOL, 0)
-#endif
- ZEND_ARG_TYPE_INFO(0, rpmtag, IS_LONG, 0)
-ZEND_END_ARG_INFO()
-
/* {{{ proto int rpmaddtag(int tag)
add a tag in the default set */
PHP_FUNCTION(rpmaddtag)
@@ -542,7 +550,12 @@ PHP_FUNCTION(rpmaddtag)
zend_long tag;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &tag) == FAILURE) {
- return;
+ RETURN_THROWS();
+ }
+
+ if (rpmTagGetType(tag) == RPM_NULL_TYPE) {
+ zend_argument_value_error(1, "Unkown rpmtag");
+ RETURN_THROWS();
}
if (RPMINFO_G(tags)) {
@@ -551,8 +564,10 @@ PHP_FUNCTION(rpmaddtag)
RETURN_BOOL(0);
}
}
- RPMINFO_G(max_tags) += 16;
- RPMINFO_G(tags) = erealloc(RPMINFO_G(tags), RPMINFO_G(max_tags) * sizeof(rpmTagVal));
+ if (RPMINFO_G(nb_tags) == RPMINFO_G(max_tags)) {
+ RPMINFO_G(max_tags) += 16;
+ RPMINFO_G(tags) = erealloc(RPMINFO_G(tags), RPMINFO_G(max_tags) * sizeof(rpmTagVal));
+ }
} else {
RPMINFO_G(max_tags) = 16;
RPMINFO_G(tags) = emalloc(RPMINFO_G(max_tags) * sizeof(rpmTagVal));
@@ -563,12 +578,308 @@ PHP_FUNCTION(rpmaddtag)
}
/* }}} */
+#ifdef HAVE_ARCHIVE
+static ssize_t php_rpm_ops_read(php_stream *stream, char *buf, size_t count)
+{
+ ssize_t n = -1;
+ STREAM_DATA_FROM_STREAM();
+
+ if (self) {
+ n = rpmfiArchiveRead(self->fi, buf, count);
+ if (n == 0 || n < (ssize_t)count) {
+ stream->eof = 1;
+ }
+ }
+ return n;
+}
+
+static void php_rpm_ops_free(struct php_rpm_stream_data_t *self, int close_handle)
+{
+ if (self) {
+ if (close_handle) {
+ Fclose(self->gzdi);
+ rpmfilesFree(self->files);
+ rpmfiFree(self->fi);
+ headerFree(self->h);
+ }
+ efree(self);
+ }
+}
+
+static int php_rpm_ops_close(php_stream *stream, int close_handle)
+{
+ STREAM_DATA_FROM_STREAM();
+
+ php_rpm_ops_free(self, close_handle);
+ stream->abstract = NULL;
+
+ return EOF;
+}
+
+static int php_zip_ops_stat(php_stream *stream, php_stream_statbuf *ssb)
+{
+ STREAM_DATA_FROM_STREAM();
+
+ if (self) {
+ struct stat s[2]; /* librpm may use different size (32-bit) */
+ int rc;
+ rc = rpmfiStat(self->fi, 0, s);
+ memcpy(&ssb->sb, s, sizeof(ssb->sb));
+ return rc;
+ }
+ return -1;
+}
+
+const php_stream_ops php_stream_rpmio_ops = {
+ NULL, /* write */
+ php_rpm_ops_read,
+ php_rpm_ops_close,
+ NULL, /* flush */
+ "rpm",
+ NULL, /* seek */
+ NULL, /* cast */
+ php_zip_ops_stat,
+ NULL /* set_option */
+};
+
+static struct php_rpm_stream_data_t *php_stream_rpm_finder(const char *path, int want_content)
+{
+ size_t path_len;
+ zend_string *file_basename;
+ char file_dirname[MAXPATHLEN];
+ char *fragment;
+ size_t fragment_len;
+ struct php_rpm_stream_data_t *self = NULL;
+ FD_t fdi;
+ FD_t gzdi;
+ int rc;
+ Header h;
+ char rpmio_flags[80];
+ const char *compr;
+ rpmfiles files;
+ rpmfi fi;
+ rpmts ts = rpminfo_getts();
+
+ fragment = strchr(path, '#');
+ if (!fragment) {
+ return NULL;
+ }
+ if (strncasecmp("rpm://", path, 6) == 0) {
+ path += 6;
+ }
+ fragment_len = strlen(fragment);
+ if (fragment_len < 1) {
+ return NULL;
+ }
+ path_len = strlen(path);
+ if (path_len >= MAXPATHLEN) {
+ return NULL;
+ }
+ memcpy(file_dirname, path, path_len - fragment_len);
+ file_dirname[path_len - fragment_len] = '\0';
+ file_basename = php_basename(path, path_len - fragment_len, NULL, 0);
+ fragment++;
+ if (php_check_open_basedir(file_dirname)) {
+ zend_string_release_ex(file_basename, 0);
+ return NULL;
+ }
+ fdi = Fopen(file_dirname, "r.ufdio");
+ if (Ferror(fdi)) {
+ zend_string_release_ex(file_basename, 0);
+ return NULL;
+ }
+ rc = rpmReadPackageFile(ts, fdi, "rpm2cpio", &h);
+ if (rc != RPMRC_OK && rc != RPMRC_NOKEY && rc != RPMRC_NOTTRUSTED) {
+ zend_string_release_ex(file_basename, 0);
+ Fclose(fdi);
+ return NULL;
+ }
+
+ compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR);
+ snprintf(rpmio_flags, sizeof(rpmio_flags), "r.%s", compr ? compr : "gzip");
+ gzdi = Fdopen(fdi, rpmio_flags);
+ if (gzdi == NULL) {
+ headerFree(h);
+ Fclose(fdi);
+ zend_string_release_ex(file_basename, 0);
+ return NULL;
+ }
+
+ files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER);
+ fi = rpmfiNewArchiveReader(gzdi, files, RPMFI_ITER_READ_ARCHIVE);
+
+ while((rc = rpmfiNext(fi)) >=0) {
+ const char *fn = rpmfiFN(fi);
+ /*
+ printf("Name=%s, Size=%d, N=%d, mode=%d, reg=%d, content=%d, rdev=%d, inode=%d link=%s\n", fn,
+ (int)rpmfiFSize(fi), (int)rpmfiFNlink(fi), (int)rpmfiFMode(fi),
+ (int)S_ISREG(rpmfiFMode(fi)), (int)rpmfiArchiveHasContent(fi),
+ (int)rpmfiFRdev(fi), (int)rpmfiFInode(fi), rpmfiFLink(fi));
+ */
+ if (!strcmp(fn, fragment)) {
+ if (want_content && S_ISREG(rpmfiFMode(fi)) && !rpmfiArchiveHasContent(fi)) {
+ rpm_rdev_t rdev = rpmfiFRdev(fi);
+ rpm_ino_t inode = rpmfiFInode(fi);
+ while((rc = rpmfiNext(fi)) >=0) {
+ if (rdev == rpmfiFRdev(fi) && inode == rpmfiFInode(fi) && rpmfiArchiveHasContent(fi)) {
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ if (rc == RPMERR_ITER_END) {
+ Fclose(gzdi);
+ rpmfilesFree(files);
+ rpmfiFree(fi);
+ headerFree(h);
+ } else {
+ self = emalloc(sizeof(*self));
+ self->gzdi = gzdi;
+ self->files = files;
+ self->fi = fi;
+ self->h = h;
+ }
+ zend_string_release_ex(file_basename, 0);
+
+ return self;
+}
+
+php_stream *php_stream_rpm_opener(php_stream_wrapper *wrapper,
+ const char *path,
+ const char *mode,
+ int options,
+ zend_string **opened_path,
+ php_stream_context *context STREAMS_DC)
+{
+ struct php_rpm_stream_data_t *self;
+
+ if (mode[0] != 'r') {
+ return NULL;
+ }
+ self = php_stream_rpm_finder(path, 1);
+ if (self) {
+ if (opened_path) {
+ *opened_path = zend_string_init(path, strlen(path), 0);
+ }
+ if (!S_ISREG(rpmfiFMode(self->fi)) || !rpmfiArchiveHasContent(self->fi)) {
+ php_rpm_ops_free(self, 1);
+ } else {
+ return php_stream_alloc(&php_stream_rpmio_ops, self, NULL, mode);
+ }
+ }
+
+ return NULL;
+}
+
+static int php_stream_rpm_stat(php_stream_wrapper *wrapper, const char *url, int flags,
+ php_stream_statbuf *ssb, php_stream_context *context)
+{
+ struct php_rpm_stream_data_t *self;
+ int rc = -1;
+
+ self = php_stream_rpm_finder(url, 0);
+ if (self) {
+ struct stat s[2]; /* librpm may use different size (32-bit) */
+ rc = rpmfiStat(self->fi, 0, s);
+ memcpy(&ssb->sb, s, sizeof(ssb->sb));
+ php_rpm_ops_free(self, 1);
+ }
+
+ return rc;
+}
+
+static const php_stream_wrapper_ops rpm_stream_wops = {
+ php_stream_rpm_opener,
+ NULL, /* close */
+ NULL, /* fstat */
+ php_stream_rpm_stat,
+ NULL, /* opendir */
+ "RPM wrapper",
+ NULL, /* unlink */
+ NULL, /* rename */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL /* metadata */
+};
+
+const php_stream_wrapper php_stream_rpm_wrapper = {
+ &rpm_stream_wops,
+ NULL,
+ 0 /* is_url */
+};
+
+/* {{{ proto array rpmgetsymlink(string path , string name)
+ Retrieve soft link target of en entry */
+PHP_FUNCTION(rpmgetsymlink)
+{
+ char *path, *name;
+ const char *link;
+ size_t plen, nlen;
+ FD_t fdi;
+ FD_t gzdi;
+ int rc;
+ Header h;
+ char rpmio_flags[80];
+ const char *compr;
+ rpmfiles files;
+ rpmfi fi;
+ rpmts ts = rpminfo_getts();
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &path, &plen, &name, &nlen) == FAILURE) {
+ RETURN_THROWS();
+ }
+
+ if (php_check_open_basedir(path)) {
+ RETURN_NULL();
+ }
+ fdi = Fopen(path, "r.ufdio");
+ if (Ferror(fdi)) {
+ RETURN_NULL();
+ }
+ rc = rpmReadPackageFile(ts, fdi, "rpm2cpio", &h);
+ if (rc != RPMRC_OK && rc != RPMRC_NOKEY && rc != RPMRC_NOTTRUSTED) {
+ Fclose(fdi);
+ RETURN_NULL();
+ }
+
+ compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR);
+ snprintf(rpmio_flags, sizeof(rpmio_flags), "r.%s", compr ? compr : "gzip");
+ gzdi = Fdopen(fdi, rpmio_flags);
+ if (gzdi == NULL) {
+ headerFree(h);
+ Fclose(fdi);
+ RETURN_NULL();
+ }
+
+ files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER);
+ fi = rpmfiNewArchiveReader(gzdi, files, RPMFI_ITER_READ_ARCHIVE);
+
+ rc = rpmfiFindFN(fi, name);
+ rpmfiSetFX(fi, rc); /* return value have change in 4.18 (from previous to new) */
+ if (rc < 0
+ || rpmfiFX(fi) != rc
+ || (link = rpmfiFLink(fi)) == NULL) {
+ RETVAL_NULL();
+ } else {
+ RETVAL_STRING(link);
+ }
+ rpmfiFree(fi);
+ rpmfilesFree(files);
+ headerFree(h);
+ Fclose(gzdi);
+}
+/* }}} */
+#endif /* HAVE_ARCHIVE */
+
+
/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(rpminfo)
{
- const char *tagname;
- rpmtd names;
+ const char *tagname;
+ rpmtd names;
REGISTER_STRING_CONSTANT("RPMVERSION", (char *)RPMVERSION, CONST_CS | CONST_PERSISTENT);
@@ -608,6 +919,10 @@ PHP_MINIT_FUNCTION(rpminfo)
}
rpmtdFree(names);
+#ifdef HAVE_ARCHIVE
+ php_register_url_stream_wrapper("rpm", &php_stream_rpm_wrapper);
+#endif
+
return SUCCESS;
}
/* }}} */
@@ -659,7 +974,14 @@ PHP_MINFO_FUNCTION(rpminfo)
php_info_print_table_start();
php_info_print_table_header(2, "rpminfo support", "enabled");
php_info_print_table_row(2, "Extension version", PHP_RPMINFO_VERSION);
+ php_info_print_table_row(2, "Author", PHP_RPMINFO_AUTHOR);
+ php_info_print_table_row(2, "License", PHP_RPMINFO_LICENSE);
php_info_print_table_row(2, "RPM library version", RPMVERSION);
+#ifdef HAVE_ARCHIVE
+ php_info_print_table_row(2, "RPM stream wrapper", "yes");
+#else
+ php_info_print_table_row(2, "RPM stream wrapper", "no");
+#endif
php_info_print_table_end();
/* Remove comments if you have entries in php.ini
@@ -680,20 +1002,6 @@ static PHP_GINIT_FUNCTION(rpminfo) /* {{{ */
}
/* }}} */
-/* {{{ rpminfo_functions[]
- *
- * Every user visible function must have an entry in rpminfo_functions[].
- */
-const zend_function_entry rpminfo_functions[] = {
- PHP_FE(rpmaddtag, arginfo_rpmaddtag)
- PHP_FE(rpmdbinfo, arginfo_rpmdbinfo)
- PHP_FE(rpmdbsearch, arginfo_rpmdbsearch)
- PHP_FE(rpminfo, arginfo_rpminfo)
- PHP_FE(rpmvercmp, arginfo_rpmvercmp)
- PHP_FE_END
-};
-/* }}} */
-
/* {{{ rpminfo_module_entry
*/
zend_module_entry rpminfo_module_entry = {
@@ -701,7 +1009,7 @@ zend_module_entry rpminfo_module_entry = {
NULL,
NULL,
"rpminfo",
- rpminfo_functions,
+ ext_functions,
PHP_MINIT(rpminfo),
NULL,
PHP_RINIT(rpminfo),
diff --git a/rpminfo.stub.php b/rpminfo.stub.php
new file mode 100644
index 0000000..7e6e32e
--- /dev/null
+++ b/rpminfo.stub.php
@@ -0,0 +1,17 @@
+<?php
+
+/** @generate-function-entries */
+
+function rpmaddtag(int $rpmtag): bool {}
+
+function rpmdbinfo(string $nevr, bool $full = false): Array|null {}
+
+function rpmdbsearch(string $pattern, int $rpmtag = RPMTAG_NAME, int $rpmmire = -1, bool $full = false): Array|null {}
+
+function rpminfo(string $path, bool $full = false, ?string &$error = null): Array|null {}
+
+function rpmvercmp(string $evr1, string $evr2, ?string $operator = null): int|bool {}
+
+#ifdef HAVE_ARCHIVE
+function rpmgetsymlink(string $path, string $name): string|null {}
+#endif
diff --git a/rpminfo_arginfo.h b/rpminfo_arginfo.h
new file mode 100644
index 0000000..aa0e1d3
--- /dev/null
+++ b/rpminfo_arginfo.h
@@ -0,0 +1,60 @@
+/* This is a generated file, edit the .stub.php file instead.
+ * Stub hash: e65e33b4f6ebedcc7ef3030714ebf7e4c06f2778 */
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmaddtag, 0, 1, _IS_BOOL, 0)
+ ZEND_ARG_TYPE_INFO(0, rpmtag, IS_LONG, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmdbinfo, 0, 1, IS_ARRAY, 1)
+ ZEND_ARG_TYPE_INFO(0, nevr, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, full, _IS_BOOL, 0, "false")
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmdbsearch, 0, 1, IS_ARRAY, 1)
+ ZEND_ARG_TYPE_INFO(0, pattern, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, rpmtag, IS_LONG, 0, "RPMTAG_NAME")
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, rpmmire, IS_LONG, 0, "-1")
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, full, _IS_BOOL, 0, "false")
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpminfo, 0, 1, IS_ARRAY, 1)
+ ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, full, _IS_BOOL, 0, "false")
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(1, error, IS_STRING, 1, "null")
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_rpmvercmp, 0, 2, MAY_BE_LONG|MAY_BE_BOOL)
+ ZEND_ARG_TYPE_INFO(0, evr1, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO(0, evr2, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, operator, IS_STRING, 1, "null")
+ZEND_END_ARG_INFO()
+
+#if defined(HAVE_ARCHIVE)
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmgetsymlink, 0, 2, IS_STRING, 1)
+ ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
+ZEND_END_ARG_INFO()
+#endif
+
+
+ZEND_FUNCTION(rpmaddtag);
+ZEND_FUNCTION(rpmdbinfo);
+ZEND_FUNCTION(rpmdbsearch);
+ZEND_FUNCTION(rpminfo);
+ZEND_FUNCTION(rpmvercmp);
+#if defined(HAVE_ARCHIVE)
+ZEND_FUNCTION(rpmgetsymlink);
+#endif
+
+
+static const zend_function_entry ext_functions[] = {
+ ZEND_FE(rpmaddtag, arginfo_rpmaddtag)
+ ZEND_FE(rpmdbinfo, arginfo_rpmdbinfo)
+ ZEND_FE(rpmdbsearch, arginfo_rpmdbsearch)
+ ZEND_FE(rpminfo, arginfo_rpminfo)
+ ZEND_FE(rpmvercmp, arginfo_rpmvercmp)
+#if defined(HAVE_ARCHIVE)
+ ZEND_FE(rpmgetsymlink, arginfo_rpmgetsymlink)
+#endif
+ ZEND_FE_END
+};
diff --git a/tests/002-rpmvercmp.phpt b/tests/002-rpmvercmp.phpt
index d83cf67..c53a183 100644
--- a/tests/002-rpmvercmp.phpt
+++ b/tests/002-rpmvercmp.phpt
@@ -29,11 +29,53 @@ $ok = true;
foreach ($cases as $case) {
list($a,$b,$expected) = $case;
$result = rpmvercmp($a,$b);
- if ($result != $expected) {
+ if ($result !== $expected) {
$ok = false;
printf("rpmvercmp(%s, %s) = %d when %d expected\n", $a, $b, $result, $expected);
}
}
+
+$cases = [
+ ['1', '2', '>', false],
+ ['1', '2', 'gt', false],
+ ['1', '2', '>=', false],
+ ['1', '2', 'ge', false],
+ ['1', '1', '>=', true],
+ ['1', '1', 'ge', true],
+
+ ['1', '2', '<', true],
+ ['1', '2', 'lt', true],
+ ['1', '2', '<=', true],
+ ['1', '2', 'le', true],
+ ['1', '1', '<=', true],
+ ['1', '1', 'le', true],
+
+ ['1', '1', '=', true],
+ ['1', '1', '==', true],
+ ['1', '1', 'eq', true],
+
+ ['1', '2', '=', false],
+ ['1', '2', '==', false],
+ ['1', '2', 'eq', false],
+
+ ['1', '1', '!=', false],
+ ['1', '1', '<>', false],
+ ['1', '1', 'ne', false],
+
+ ['1', '2', '!=', true],
+ ['1', '2', '<>', true],
+ ['1', '2', 'ne', true],
+];
+foreach ($cases as $case) {
+ list($a,$b,$op,$expected) = $case;
+ $result = rpmvercmp($a,$b,$op);
+ if ($result !== $expected) {
+ $ok = false;
+ printf("rpmvercmp(%s, %s, %s) = %s when %s expected\n",
+ $a, $b, $op, $result ? "true" : "false", $expected ? "true" : "false");
+ }
+}
+
if ($ok) echo "OK\n";
?>
Done
diff --git a/tests/003-rpminfo.phpt b/tests/003-rpminfo.phpt
index beb7b47..919bcc9 100644
--- a/tests/003-rpminfo.phpt
+++ b/tests/003-rpminfo.phpt
@@ -14,7 +14,7 @@ array(5) {
["Version"]=>
string(1) "1"
["Release"]=>
- string(11) "1.fc27.remi"
+ string(1) "3"
["Summary"]=>
string(5) "Bidon"
["Arch"]=>
diff --git a/tests/005-rpminfo-full.phpt b/tests/005-rpminfo-full.phpt
index 38f5bab..15c158d 100644
--- a/tests/005-rpminfo-full.phpt
+++ b/tests/005-rpminfo-full.phpt
@@ -24,9 +24,11 @@ Done
--- bidon.rpm ---
string(5) "bidon"
string(15) "A dummy package"
-array(1) {
+array(2) {
[0]=>
- string(8) "- create"
+ string(14) "- add symlinks"
+ [1]=>
+ string(20) "- add some hardlinks"
}
bool(false)
array(1) {
diff --git a/tests/010-rpmvercmp_error7.phpt b/tests/010-rpmvercmp_error7.phpt
deleted file mode 100644
index c9b3875..0000000
--- a/tests/010-rpmvercmp_error7.phpt
+++ /dev/null
@@ -1,25 +0,0 @@
---TEST--
-Check for rpmvercmp function error
---SKIPIF--
-<?php
-if (!extension_loaded("rpminfo")) print "skip";
-if (PHP_VERSION_ID >= 80000) print "skip only for PHP 7";
-?>
---FILE--
-<?php
-var_dump(rpmvercmp());
-var_dump(rpmvercmp("a"));
-var_dump(rpmvercmp("a", "b", "c"));
-?>
-Done
---EXPECTF--
-
-Warning: rpmvercmp() expects exactly 2 parameters, 0 given in %s
-NULL
-
-Warning: rpmvercmp() expects exactly 2 parameters, 1 given in %s
-NULL
-
-Warning: rpmvercmp() expects exactly 2 parameters, 3 given in %s
-NULL
-Done
diff --git a/tests/011-rpmvercmp_error8.phpt b/tests/011-rpmvercmp_error8.phpt
index 3db3564..2fe6aa1 100644
--- a/tests/011-rpmvercmp_error8.phpt
+++ b/tests/011-rpmvercmp_error8.phpt
@@ -19,13 +19,19 @@ try {
}
try {
var_dump(rpmvercmp("a", "b", "c"));
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ var_dump(rpmvercmp("a", "b", "c", "d"));
} catch (ArgumentCountError $e) {
echo $e->getMessage(), "\n";
}
?>
Done
--EXPECTF--
-rpmvercmp() expects exactly 2 parameters, 0 given
-rpmvercmp() expects exactly 2 parameters, 1 given
-rpmvercmp() expects exactly 2 parameters, 3 given
+rpmvercmp() expects at least 2 %s, 0 given
+rpmvercmp() expects at least 2 %s, 1 given
+rpmvercmp(): Argument #3 ($operator) must be a valid comparison operator
+rpmvercmp() expects at most 3 %s, 4 given
Done
diff --git a/tests/012-rpmaddtag.phpt b/tests/012-rpmaddtag.phpt
new file mode 100644
index 0000000..bf371a4
--- /dev/null
+++ b/tests/012-rpmaddtag.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Check for rpmaddtag parameter check
+--SKIPIF--
+<?php if (!extension_loaded("rpminfo")) print "skip"; ?>
+--FILE--
+<?php
+var_dump(rpmaddtag(RPMTAG_INSTALLTIME));
+try {
+ var_dump(rpmaddtag(-1));
+} catch (ValueError $e) {
+ echo $e->getMessage();
+}
+?>
+--EXPECTF--
+bool(true)
+%A Unkown rpmtag%A
diff --git a/tests/013-rpmdbsearch-error.phpt b/tests/013-rpmdbsearch-error.phpt
new file mode 100644
index 0000000..4a61227
--- /dev/null
+++ b/tests/013-rpmdbsearch-error.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Check for rpmdbinfo function
+--SKIPIF--
+<?php if (!extension_loaded("rpminfo")) print "skip"; ?>
+--FILE--
+<?php
+var_dump(rpmdbsearch('notexists', RPMTAG_NAME));
+try {
+var_dump(rpmdbsearch('notexists', RPMTAG_NAME, 99));
+} catch (ValueError $e) {
+ echo $e->getMessage();
+}
+?>
+--EXPECTF--
+NULL
+%A Unkown rpmmire%A
diff --git a/tests/014-stream.phpt b/tests/014-stream.phpt
new file mode 100644
index 0000000..69b881e
--- /dev/null
+++ b/tests/014-stream.phpt
@@ -0,0 +1,97 @@
+--TEST--
+Check for stream
+--SKIPIF--
+<?php
+if (!extension_loaded("rpminfo")) print "skip";
+if (version_compare(RPMVERSION, '4.13', 'lt')) print("skip librpm is older than 4.13");
+?>
+--FILE--
+<?php
+$d = "rpm://" . __DIR__ . "/bidon.rpm#/usr/share/doc/bidon";
+$n = "rpm://" . __DIR__ . "/bidon.rpm#/usr/share/doc/bidon/README";
+$x = "rpm://" . __DIR__ . "/bidon.rpm#/usr/share/doc/bidon/MISSING";
+$foo = "rpm://" . __DIR__ . "/bidon.rpm#/etc/foo.conf";
+$bar = "rpm://" . __DIR__ . "/bidon.rpm#/etc/bar.conf";
+$toto = "rpm://" . __DIR__ . "/bidon.rpm#/etc/toto.conf";
+
+echo "+ wrapper\n";
+var_dump(in_array('rpm', stream_get_wrappers()));
+
+echo "+ stat\n";
+$s = stat($d); // S_ISDIR
+var_dump($s['size'], $s['mode'] , ($s['mode'] & 0170000) == 0040000 ? "OK" : "KO");
+var_dump(file_exists($d), is_dir($d), is_file($d), is_link($n));
+$s = stat($n); // S_ISREG
+var_dump($s['size'], $s['mode'] , ($s['mode'] & 0170000) == 0100000 ? "OK" : "KO");
+var_dump(file_exists($n), is_dir($n), is_file($n), is_link($n));
+$s = stat($toto); // S_ISLNK
+var_dump($s['size'], $s['mode'] , ($s['mode'] & 0170000) == 0120000 ? "OK" : "KO");
+var_dump(file_exists($toto), is_dir($toto), is_file($toto), is_link($toto));
+
+echo "+ file\n";
+var_dump($f = fopen($n, "r"));
+$s = fstat($f);
+var_dump($s['size'], $s['mode']);
+var_dump(trim(fread($f, 10)));
+var_dump(feof($f));
+var_dump(trim(fread($f, 100)));
+var_dump(feof($f));
+fclose($f);
+
+echo "+ stream\n";
+var_dump(trim(file_get_contents($n))); // Existing file
+var_dump(trim(file_get_contents($foo))); // Hardlink with content
+var_dump(trim(file_get_contents($bar))); // hardlink without content
+var_dump(file_get_contents($x)); // Missing file
+
+echo "+ symlink\n";
+var_dump(rpmgetsymlink(__DIR__ . "/bidon.rpm", "missing"));
+var_dump(rpmgetsymlink(__DIR__ . "/bidon.rpm", "/etc/foo.conf")); // not a symlink
+var_dump(rpmgetsymlink(__DIR__ . "/bidon.rpm", "/etc/toto.conf")); // symlink
+?>
+Done
+--EXPECTF--
++ wrapper
+bool(true)
++ stat
+int(0)
+int(16877)
+string(2) "OK"
+bool(true)
+bool(true)
+bool(false)
+bool(false)
+int(30)
+int(33188)
+string(2) "OK"
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+int(8)
+int(41471)
+string(2) "OK"
+bool(true)
+bool(false)
+bool(false)
+bool(true)
++ file
+resource(%d) of type (stream)
+int(30)
+int(33188)
+string(10) "Thu Oct 19"
+bool(false)
+string(18) "12:01:02 CEST 2023"
+bool(true)
++ stream
+string(29) "Thu Oct 19 12:01:02 CEST 2023"
+string(7) "content"
+string(7) "content"
+
+Warning: file_get_contents(%s/bidon.rpm#/usr/share/doc/bidon/MISSING): Failed to open stream: operation failed in %s on line %d
+bool(false)
++ symlink
+NULL
+string(0) ""
+string(8) "foo.conf"
+Done
diff --git a/tests/bidon.rpm b/tests/bidon.rpm
index 6cbfa04..d8efb06 100644
--- a/tests/bidon.rpm
+++ b/tests/bidon.rpm
Binary files differ
diff --git a/tests/bidon.spec b/tests/bidon.spec
new file mode 100644
index 0000000..b3ad790
--- /dev/null
+++ b/tests/bidon.spec
@@ -0,0 +1,42 @@
+%{!?ver: %global ver 1}
+
+Name: bidon
+Version: %{ver}
+Release: 3
+Summary: Bidon
+License: Public Domain
+URL: https://rpms.remirepo.net/
+
+Obsoletes: fooobs < 2
+
+
+%description
+A dummy package
+
+%prep
+date >README
+echo "content" >conf
+
+%build
+: nothing to build
+
+%install
+install -Dpm644 conf %{buildroot}%{_sysconfdir}/foo.conf
+cd %{buildroot}%{_sysconfdir}
+ln foo.conf bar.conf
+ln -s foo.conf toto.conf
+
+%files
+%doc README
+%config(noreplace) %{_sysconfdir}/*.conf
+
+
+%changelog
+* Thu Oct 19 2023 Remi Collet <remi@fedoraproject.org> - 1-3
+- add symlinks
+
+* Fri Oct 13 2023 Remi Collet <remi@fedoraproject.org> - 1-2
+- add some hardlinks
+
+* Wed Dec 24 2014 Remi Collet <remi@fedoraproject.org> - 1-1
+- create