Skip to content

Commit 6ac3f7c

Browse files
Yurunsoftcmb69
authored andcommitted
Fix phpGH-9411: PgSQL large object resource is incorrectly closed
Co-authored-by: Christoph M. Becker <[email protected]> Closes phpGH-9411.
1 parent 81cb005 commit 6ac3f7c

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
@@ -18,6 +18,10 @@ PHP NEWS
1818
. Fixed bug #77780 ("Headers already sent..." when previous connection was
1919
aborted). (Jakub Zelenka)
2020

21+
- PDO_PGSQL:
22+
. Fixed bug GH-9411 (PgSQL large object resource is incorrectly closed).
23+
(Yurunsoft)
24+
2125
- Reflection:
2226
. Fixed bug GH-8932 (ReflectionFunction provides no way to get the called
2327
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 int pgsql_handle_in_transaction(pdo_dbh_t *dbh);
39+
3840
static char * _pdo_pgsql_trim_message(const char *message, int persistent)
3941
{
4042
register int i = strlen(message)-1;
@@ -141,10 +143,12 @@ static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count)
141143
static int pgsql_lob_close(php_stream *stream, int close_handle)
142144
{
143145
struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract;
146+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(&self->dbh))->driver_data;
144147

145148
if (close_handle) {
146149
lo_close(self->conn, self->lfd);
147150
}
151+
zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream));
148152
zval_ptr_dtor(&self->dbh);
149153
efree(self);
150154
return 0;
@@ -195,6 +199,7 @@ php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid)
195199

196200
if (stm) {
197201
Z_ADDREF_P(dbh);
202+
zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res);
198203
return stm;
199204
}
200205

@@ -203,10 +208,29 @@ php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid)
203208
}
204209
/* }}} */
205210

211+
void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh)
212+
{
213+
zend_resource *res;
214+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
215+
if (H->lob_streams) {
216+
ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) {
217+
if (res->type >= 0) {
218+
zend_list_close(res);
219+
}
220+
} ZEND_HASH_FOREACH_END();
221+
}
222+
}
223+
206224
static int pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */
207225
{
208226
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
209227
if (H) {
228+
if (H->lob_streams) {
229+
pdo_pgsql_close_lob_streams(dbh);
230+
zend_hash_destroy(H->lob_streams);
231+
pefree(H->lob_streams, dbh->is_persistent);
232+
H->lob_streams = NULL;
233+
}
210234
if (H->server) {
211235
PQfinish(H->server);
212236
H->server = NULL;
@@ -298,6 +322,8 @@ static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_l
298322
zend_long ret = 1;
299323
ExecStatusType qs;
300324

325+
bool in_trans = pgsql_handle_in_transaction(dbh);
326+
301327
if (!(res = PQexec(H->server, sql))) {
302328
/* fatal error */
303329
pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
@@ -316,6 +342,9 @@ static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_l
316342
ret = Z_L(0);
317343
}
318344
PQclear(res);
345+
if (in_trans && !pgsql_handle_in_transaction(dbh)) {
346+
pdo_pgsql_close_lob_streams(dbh);
347+
}
319348

320349
return ret;
321350
}
@@ -501,9 +530,7 @@ static int pdo_pgsql_check_liveness(pdo_dbh_t *dbh)
501530

502531
static int pgsql_handle_in_transaction(pdo_dbh_t *dbh)
503532
{
504-
pdo_pgsql_db_handle *H;
505-
506-
H = (pdo_pgsql_db_handle *)dbh->driver_data;
533+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
507534

508535
return PQtransactionStatus(H->server) > PQTRANS_IDLE;
509536
}
@@ -536,7 +563,9 @@ static int pgsql_handle_commit(pdo_dbh_t *dbh)
536563

537564
/* When deferred constraints are used the commit could
538565
fail, and a ROLLBACK implicitly ran. See bug #67462 */
539-
if (!ret) {
566+
if (ret) {
567+
pdo_pgsql_close_lob_streams(dbh);
568+
} else {
540569
dbh->in_txn = pgsql_handle_in_transaction(dbh);
541570
}
542571

@@ -545,7 +574,13 @@ static int pgsql_handle_commit(pdo_dbh_t *dbh)
545574

546575
static int pgsql_handle_rollback(pdo_dbh_t *dbh)
547576
{
548-
return pdo_pgsql_transaction_cmd("ROLLBACK", dbh);
577+
int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh);
578+
579+
if (ret) {
580+
pdo_pgsql_close_lob_streams(dbh);
581+
}
582+
583+
return ret;
549584
}
550585

551586
/* {{{ Returns true if the copy worked fine or false if error */
@@ -1233,6 +1268,8 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{
12331268
}
12341269

12351270
H->server = PQconnectdb(conn_str);
1271+
H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent);
1272+
zend_hash_init(H->lob_streams, 0, NULL, NULL, 1);
12361273

12371274
if (tmp_user) {
12381275
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
zend_bool emulate_prepares;
4646
zend_bool disable_native_prepares; /* deprecated since 5.6 */
4747
zend_bool disable_prepares;
48+
HashTable *lob_streams;
4849
} pdo_pgsql_db_handle;
4950

5051
typedef struct {
@@ -109,5 +110,6 @@ php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid);
109110
extern const php_stream_ops pdo_pgsql_lob_stream_ops;
110111

111112
void pdo_libpq_version(char *buf, size_t len);
113+
void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh);
112114

113115
#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)