@@ -187,86 +187,184 @@ function syslog_partition_manage() {
187187}
188188
189189/**
190- * This function will create a new partition for the specified table.
190+ * Validate tables that support partition maintenance.
191+ *
192+ * Any value added to the allowlist MUST match ^[a-z_]+$ so it is safe
193+ * for identifier interpolation in DDL statements (MySQL does not support
194+ * parameter binding for identifiers).
195+ */
196+ function syslog_partition_table_allowed ($ table ) {
197+ if (!in_array ($ table , array ('syslog ' , 'syslog_removed ' ), true )) {
198+ return false ;
199+ }
200+
201+ /* Defense-in-depth: reject values unsafe for identifier interpolation. */
202+ if (!preg_match ('/^[a-z_]+$/ ' , $ table )) {
203+ return false ;
204+ }
205+
206+ return true ;
207+ }
208+
209+ /**
210+ * Create a new partition for the specified table.
211+ *
212+ * @return bool true on success, false on lock failure or disallowed table.
191213 */
192214function syslog_partition_create ($ table ) {
193215 global $ syslogdb_default ;
194216
195- /* determine the format of the table name */
196- $ time = time ();
197- $ cformat = 'd ' . date ('Ymd ' , $ time );
198- $ lnow = date ('Y-m-d ' , $ time +86400 );
217+ if (!syslog_partition_table_allowed ($ table )) {
218+ return false ;
219+ }
199220
200- $ exists = syslog_db_fetch_row ("SELECT *
201- FROM `information_schema`.`partitions`
202- WHERE table_schema=' " . $ syslogdb_default . "'
203- AND partition_name=' " . $ cformat . "'
204- AND table_name='syslog'
205- ORDER BY partition_ordinal_position " );
221+ /* Hash to guarantee the lock name stays within MySQL's 64-byte limit. */
222+ $ lock_name = substr (hash ('sha256 ' , $ syslogdb_default . '.syslog_partition_create. ' . $ table ), 0 , 60 );
206223
207- if (!cacti_sizeof ($ exists )) {
208- cacti_log ("SYSLOG: Creating new partition ' $ cformat' " , false , 'SYSTEM ' );
224+ /*
225+ * 10-second timeout is sufficient: partition maintenance runs once per
226+ * poller cycle (typically 5 minutes), so sustained contention is not
227+ * expected. A failure is logged so monitoring can detect repeated misses.
228+ */
229+ $ locked = syslog_db_fetch_cell_prepared ('SELECT GET_LOCK(?, 10) ' , array ($ lock_name ));
209230
210- syslog_debug ("Creating new partition ' $ cformat' " );
231+ if ($ locked === null ) {
232+ /* NULL means the GET_LOCK call itself failed, not just contention. */
233+ cacti_log ("SYSLOG: GET_LOCK call failed for partition create on ' $ table' " , false , 'SYSTEM ' );
234+ return false ;
235+ }
211236
212- syslog_db_execute ( " ALTER TABLE ` " . $ syslogdb_default . " `.` $ table ` REORGANIZE PARTITION dMaxValue INTO (
213- PARTITION $ cformat VALUES LESS THAN (TO_DAYS(' $ lnow ')),
214- PARTITION dMaxValue VALUES LESS THAN MAXVALUE) " ) ;
237+ if (( int ) $ locked !== 1 ) {
238+ cacti_log ( " SYSLOG: Unable to acquire partition create lock for ' $ table ' " , false , ' SYSTEM ' );
239+ return false ;
215240 }
241+
242+ try {
243+ /* determine the format of the table name */
244+ $ time = time ();
245+ $ cformat = 'd ' . date ('Ymd ' , $ time );
246+ $ lnow = date ('Y-m-d ' , $ time +86400 );
247+
248+ $ exists = syslog_db_fetch_row_prepared ("SELECT *
249+ FROM `information_schema`.`partitions`
250+ WHERE table_schema = ?
251+ AND partition_name = ?
252+ AND table_name = ?
253+ ORDER BY partition_ordinal_position " ,
254+ array ($ syslogdb_default , $ cformat , $ table ));
255+
256+ if (!cacti_sizeof ($ exists )) {
257+ cacti_log ("SYSLOG: Creating new partition ' $ cformat' " , false , 'SYSTEM ' );
258+
259+ syslog_debug ("Creating new partition ' $ cformat' " );
260+
261+ /*
262+ * MySQL does not support parameter binding for DDL identifiers
263+ * or partition definitions. $table is safe because it passed
264+ * syslog_partition_table_allowed() (two-value allowlist plus
265+ * regex guard). $cformat and $lnow derive from date() and
266+ * contain only digits, hyphens, and the letter 'd'.
267+ */
268+ syslog_db_execute ("ALTER TABLE ` " . $ syslogdb_default . "`.` $ table` REORGANIZE PARTITION dMaxValue INTO (
269+ PARTITION $ cformat VALUES LESS THAN (TO_DAYS(' $ lnow')),
270+ PARTITION dMaxValue VALUES LESS THAN MAXVALUE) " );
271+ }
272+ } finally {
273+ syslog_db_fetch_cell_prepared ('SELECT RELEASE_LOCK(?) ' , array ($ lock_name ));
274+ }
275+
276+ return true ;
216277}
217278
218279/**
219- * This function will remove all old partitions for the specified table.
280+ * Remove old partitions for the specified table.
220281 */
221282function syslog_partition_remove ($ table ) {
222283 global $ syslogdb_default ;
223284
285+ if (!syslog_partition_table_allowed ($ table )) {
286+ cacti_log ("SYSLOG: partition_remove called with disallowed table ' $ table' " , false , 'SYSTEM ' );
287+ return 0 ;
288+ }
289+
290+ $ lock_name = substr (hash ('sha256 ' , $ syslogdb_default . '.syslog_partition_remove. ' . $ table ), 0 , 60 );
291+
292+ $ locked = syslog_db_fetch_cell_prepared ('SELECT GET_LOCK(?, 10) ' , array ($ lock_name ));
293+
294+ if ($ locked === null ) {
295+ cacti_log ("SYSLOG: GET_LOCK call failed for partition remove on ' $ table' " , false , 'SYSTEM ' );
296+ return 0 ;
297+ }
298+
299+ if ((int )$ locked !== 1 ) {
300+ cacti_log ("SYSLOG: Unable to acquire partition remove lock for ' $ table' " , false , 'SYSTEM ' );
301+ return 0 ;
302+ }
303+
224304 $ syslog_deleted = 0 ;
225- $ number_of_partitions = syslog_db_fetch_assoc ("SELECT *
226- FROM `information_schema`.`partitions`
227- WHERE table_schema=' " . $ syslogdb_default . "' AND table_name='syslog'
228- ORDER BY partition_ordinal_position " );
229305
230- $ days = read_config_option ('syslog_retention ' );
306+ try {
307+ $ number_of_partitions = syslog_db_fetch_assoc_prepared ("SELECT *
308+ FROM `information_schema`.`partitions`
309+ WHERE table_schema = ? AND table_name = ?
310+ ORDER BY partition_ordinal_position " ,
311+ array ($ syslogdb_default , $ table ));
312+
313+ $ days = read_config_option ('syslog_retention ' );
231314
232- syslog_debug ("There are currently ' " . sizeof ($ number_of_partitions ) . "' Syslog Partitions, We will keep ' $ days' of them. " );
315+ syslog_debug ("There are currently ' " . sizeof ($ number_of_partitions ) . "' Syslog Partitions, We will keep ' $ days' of them. " );
233316
234- if ($ days > 0 ) {
235- $ user_partitions = sizeof ($ number_of_partitions ) - 1 ;
236- if ($ user_partitions >= $ days ) {
237- $ i = 0 ;
238- while ($ user_partitions > $ days ) {
239- $ oldest = $ number_of_partitions [$ i ];
317+ if ($ days > 0 ) {
318+ $ user_partitions = sizeof ($ number_of_partitions ) - 1 ;
319+ if ($ user_partitions >= $ days ) {
320+ $ i = 0 ;
321+ while ($ user_partitions > $ days ) {
322+ $ oldest = $ number_of_partitions [$ i ];
240323
241- cacti_log ("SYSLOG: Removing old partition ' " . $ oldest ['PARTITION_NAME ' ] . "' " , false , 'SYSTEM ' );
324+ cacti_log ("SYSLOG: Removing old partition ' " . $ oldest ['PARTITION_NAME ' ] . "' " , false , 'SYSTEM ' );
242325
243- syslog_debug ("Removing partition ' " . $ oldest ['PARTITION_NAME ' ] . "' " );
326+ syslog_debug ("Removing partition ' " . $ oldest ['PARTITION_NAME ' ] . "' " );
244327
245- syslog_db_execute ("ALTER TABLE ` " . $ syslogdb_default . "`.` $ table` DROP PARTITION " . $ oldest ['PARTITION_NAME ' ]);
328+ syslog_db_execute ("ALTER TABLE ` " . $ syslogdb_default . "`.` $ table` DROP PARTITION " . $ oldest ['PARTITION_NAME ' ]);
246329
247- $ i ++;
248- $ user_partitions --;
249- $ syslog_deleted ++;
330+ $ i ++;
331+ $ user_partitions --;
332+ $ syslog_deleted ++;
333+ }
250334 }
251335 }
336+ } finally {
337+ syslog_db_fetch_cell_prepared ('SELECT RELEASE_LOCK(?) ' , array ($ lock_name ));
252338 }
253339
254340 return $ syslog_deleted ;
255341}
256342
343+ /*
344+ * syslog_partition_check is a read-only SELECT against information_schema.
345+ * It does not execute DDL, so it does not need the named lock that
346+ * syslog_partition_create and syslog_partition_remove acquire. External
347+ * serialization is provided by the poller cycle calling
348+ * syslog_partition_manage().
349+ */
257350function syslog_partition_check ($ table ) {
258351 global $ syslogdb_default ;
259352
353+ if (!syslog_partition_table_allowed ($ table )) {
354+ return false ;
355+ }
356+
260357 if (defined ('SYSLOG_CONFIG ' )) {
261358 include (SYSLOG_CONFIG );
262359 }
263360
264361 /* find date of last partition */
265- $ last_part = syslog_db_fetch_cell ("SELECT PARTITION_NAME
362+ $ last_part = syslog_db_fetch_cell_prepared ("SELECT PARTITION_NAME
266363 FROM `information_schema`.`partitions`
267- WHERE table_schema=' " . $ syslogdb_default . " ' AND table_name='syslog'
364+ WHERE table_schema = ? AND table_name = ?
268365 ORDER BY partition_ordinal_position DESC
269- LIMIT 1,1; " );
366+ LIMIT 1,1 " ,
367+ array ($ syslogdb_default , $ table ));
270368
271369 $ lformat = str_replace ('d ' , '' , $ last_part );
272370 $ cformat = date ('Ymd ' );
@@ -2421,4 +2519,3 @@ function alert_replace_variables($alert, $results, $hostname = '') {
24212519
24222520 return $ command ;
24232521}
2424-
0 commit comments