Skip to content

Commit b5cad50

Browse files
committed
Merge branch 'PHP-8.0' into PHP-8.1
* PHP-8.0: Fix phpGH-9411: PgSQL large object resource is incorrectly closed
2 parents 59180b5 + 6ac3f7c commit b5cad50

File tree

5 files changed

+95
-5
lines changed

5 files changed

+95
-5
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ PHP NEWS
2323
- PCRE:
2424
. Fixed pcre.jit on Apple Silicon. (Niklas Keller)
2525

26+
- PDO_PGSQL:
27+
. Fixed bug GH-9411 (PgSQL large object resource is incorrectly closed).
28+
(Yurunsoft)
29+
2630
- Reflection:
2731
. Fixed bug GH-8932 (ReflectionFunction provides no way to get the called
2832
class of a Closure). (cmb, Nicolas Grekas)

ext/pdo_pgsql/pgsql_driver.c

+42-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include "zend_exceptions.h"
3636
#include "pgsql_driver_arginfo.h"
3737

38+
static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh);
39+
3840
static char * _pdo_pgsql_trim_message(const char *message, int persistent)
3941
{
4042
size_t i = strlen(message)-1;
@@ -140,10 +142,12 @@ static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count)
140142
static int pgsql_lob_close(php_stream *stream, int close_handle)
141143
{
142144
struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract;
145+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(&self->dbh))->driver_data;
143146

144147
if (close_handle) {
145148
lo_close(self->conn, self->lfd);
146149
}
150+
zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream));
147151
zval_ptr_dtor(&self->dbh);
148152
efree(self);
149153
return 0;
@@ -194,6 +198,7 @@ php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid)
194198

195199
if (stm) {
196200
Z_ADDREF_P(dbh);
201+
zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res);
197202
return stm;
198203
}
199204

@@ -202,10 +207,29 @@ php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid)
202207
}
203208
/* }}} */
204209

210+
void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh)
211+
{
212+
zend_resource *res;
213+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
214+
if (H->lob_streams) {
215+
ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) {
216+
if (res->type >= 0) {
217+
zend_list_close(res);
218+
}
219+
} ZEND_HASH_FOREACH_END();
220+
}
221+
}
222+
205223
static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */
206224
{
207225
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
208226
if (H) {
227+
if (H->lob_streams) {
228+
pdo_pgsql_close_lob_streams(dbh);
229+
zend_hash_destroy(H->lob_streams);
230+
pefree(H->lob_streams, dbh->is_persistent);
231+
H->lob_streams = NULL;
232+
}
209233
if (H->server) {
210234
PQfinish(H->server);
211235
H->server = NULL;
@@ -295,6 +319,8 @@ static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql)
295319
zend_long ret = 1;
296320
ExecStatusType qs;
297321

322+
bool in_trans = pgsql_handle_in_transaction(dbh);
323+
298324
if (!(res = PQexec(H->server, ZSTR_VAL(sql)))) {
299325
/* fatal error */
300326
pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
@@ -313,6 +339,9 @@ static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql)
313339
ret = Z_L(0);
314340
}
315341
PQclear(res);
342+
if (in_trans && !pgsql_handle_in_transaction(dbh)) {
343+
pdo_pgsql_close_lob_streams(dbh);
344+
}
316345

317346
return ret;
318347
}
@@ -503,9 +532,7 @@ static zend_result pdo_pgsql_check_liveness(pdo_dbh_t *dbh)
503532

504533
static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh)
505534
{
506-
pdo_pgsql_db_handle *H;
507-
508-
H = (pdo_pgsql_db_handle *)dbh->driver_data;
535+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
509536

510537
return PQtransactionStatus(H->server) > PQTRANS_IDLE;
511538
}
@@ -538,7 +565,9 @@ static bool pgsql_handle_commit(pdo_dbh_t *dbh)
538565

539566
/* When deferred constraints are used the commit could
540567
fail, and a ROLLBACK implicitly ran. See bug #67462 */
541-
if (!ret) {
568+
if (ret) {
569+
pdo_pgsql_close_lob_streams(dbh);
570+
} else {
542571
dbh->in_txn = pgsql_handle_in_transaction(dbh);
543572
}
544573

@@ -547,7 +576,13 @@ static bool pgsql_handle_commit(pdo_dbh_t *dbh)
547576

548577
static bool pgsql_handle_rollback(pdo_dbh_t *dbh)
549578
{
550-
return pdo_pgsql_transaction_cmd("ROLLBACK", dbh);
579+
int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh);
580+
581+
if (ret) {
582+
pdo_pgsql_close_lob_streams(dbh);
583+
}
584+
585+
return ret;
551586
}
552587

553588
/* {{{ Returns true if the copy worked fine or false if error */
@@ -1242,6 +1277,8 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{
12421277
}
12431278

12441279
H->server = PQconnectdb(conn_str);
1280+
H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent);
1281+
zend_hash_init(H->lob_streams, 0, NULL, NULL, 1);
12451282

12461283
if (tmp_user) {
12471284
zend_string_release_ex(tmp_user, 0);

ext/pdo_pgsql/pgsql_statement.c

+6
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt)
134134
pdo_pgsql_db_handle *H = S->H;
135135
ExecStatusType status;
136136

137+
bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh);
138+
137139
/* ensure that we free any previous unfetched results */
138140
if(S->result) {
139141
PQclear(S->result);
@@ -252,6 +254,10 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt)
252254
stmt->row_count = (zend_long)PQntuples(S->result);
253255
}
254256

257+
if (in_trans && !stmt->dbh->methods->in_transaction(stmt->dbh)) {
258+
pdo_pgsql_close_lob_streams(stmt->dbh);
259+
}
260+
255261
return 1;
256262
}
257263

ext/pdo_pgsql/php_pdo_pgsql_int.h

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ typedef struct {
4545
bool emulate_prepares;
4646
bool disable_native_prepares; /* deprecated since 5.6 */
4747
bool disable_prepares;
48+
HashTable *lob_streams;
4849
} pdo_pgsql_db_handle;
4950

5051
typedef struct {
@@ -106,5 +107,6 @@ php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid);
106107
extern const php_stream_ops pdo_pgsql_lob_stream_ops;
107108

108109
void pdo_libpq_version(char *buf, size_t len);
110+
void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh);
109111

110112
#endif /* PHP_PDO_PGSQL_INT_H */

ext/pdo_pgsql/tests/gh9411.phpt

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Bug GH-9411 (PgSQL large object resource is incorrectly closed)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
6+
require __DIR__ . '/config.inc';
7+
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
8+
PDOTest::skip();
9+
?>
10+
--FILE--
11+
<?php
12+
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
13+
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
14+
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
15+
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
16+
17+
$db->beginTransaction();
18+
$oid = $db->pgsqlLOBCreate();
19+
var_dump($lob = $db->pgsqlLOBOpen($oid, 'wb'));
20+
fwrite($lob, 'test');
21+
$db->rollback();
22+
var_dump($lob);
23+
24+
$db->beginTransaction();
25+
$oid = $db->pgsqlLOBCreate();
26+
var_dump($lob = $db->pgsqlLOBOpen($oid, 'wb'));
27+
fwrite($lob, 'test');
28+
$db->commit();
29+
var_dump($lob);
30+
31+
$db->beginTransaction();
32+
var_dump($lob = $db->pgsqlLOBOpen($oid, 'wb'));
33+
var_dump(fgets($lob));
34+
?>
35+
--EXPECTF--
36+
resource(%d) of type (stream)
37+
resource(%d) of type (Unknown)
38+
resource(%d) of type (stream)
39+
resource(%d) of type (Unknown)
40+
resource(%d) of type (stream)
41+
string(4) "test"

0 commit comments

Comments
 (0)