$visited IDs active in the current recursion path. * @param int $expansion Number of definitions expanded by this call. * @param array $cache Successfully resolved definitions. * @param int $cache_bytes Total bytes retained by the resolver cache. * * @return string|null The CDEF string, or null when recursion is unsafe. */ function get_cdef_recursive(int $cdef_id, array &$visited, int &$expansion, array &$cache, int &$cache_bytes) : ?string { if (isset($visited[$cdef_id])) { cacti_log(sprintf('ERROR: CDEF %d contains a recursive cycle.', $cdef_id), false, 'CDEF'); return null; } if (array_key_exists($cdef_id, $cache)) { return $cache[$cdef_id]; } if (count($visited) >= 64) { cacti_log(sprintf('ERROR: CDEF %d exceeds the resolver nesting depth.', $cdef_id), false, 'CDEF'); return null; } if (++$expansion > 4096) { cacti_log(sprintf('ERROR: CDEF %d exceeds the resolver expansion budget.', $cdef_id), false, 'CDEF'); return null; } $visited[$cdef_id] = true; $cdef_items = db_fetch_assoc_prepared('SELECT id, type, value FROM cdef_items WHERE cdef_id = ? ORDER BY sequence', [$cdef_id]); if ($cdef_items === false) { unset($visited[$cdef_id]); cacti_log(sprintf('ERROR: Unable to load CDEF %d.', $cdef_id), false, 'CDEF'); return null; } if (cacti_sizeof($cdef_items) === 0) { $cdef_exists = db_fetch_assoc_prepared('SELECT id FROM cdef WHERE id = ?', [$cdef_id]); unset($visited[$cdef_id]); if ($cdef_exists === false) { cacti_log(sprintf('ERROR: Unable to verify CDEF %d.', $cdef_id), false, 'CDEF'); return null; } if (cacti_sizeof($cdef_exists) === 0) { cacti_log(sprintf('ERROR: CDEF %d does not exist.', $cdef_id), false, 'CDEF'); return null; } $cache[$cdef_id] = ''; return $cache[$cdef_id]; } $parts = []; $length = 0; foreach ($cdef_items as $cdef_item) { if ($cdef_item['type'] == 5) { $current_cdef_id = $cdef_item['value']; $nested = get_cdef_recursive((int) $current_cdef_id, $visited, $expansion, $cache, $cache_bytes); if ($nested === null) { unset($visited[$cdef_id]); return null; } if ($nested === '') { cacti_log(sprintf('ERROR: CDEF %d references an empty definition.', $cdef_id), false, 'CDEF'); unset($visited[$cdef_id]); return null; } $length += strlen($nested) + ($parts === [] ? 0 : 1); if ($length > 1048576) { cacti_log(sprintf('ERROR: CDEF %d exceeds the resolver output budget.', $cdef_id), false, 'CDEF'); unset($visited[$cdef_id]); return null; } $parts[] = $nested; } else { $item_name = get_cdef_item_name($cdef_item['id']); if ($item_name === null) { unset($visited[$cdef_id]); return null; } if ($item_name === '') { cacti_log(sprintf('ERROR: CDEF %d contains an empty item.', $cdef_id), false, 'CDEF'); unset($visited[$cdef_id]); return null; } $length += strlen($item_name) + ($parts === [] ? 0 : 1); if ($length > 1048576) { cacti_log(sprintf('ERROR: CDEF %d exceeds the resolver output budget.', $cdef_id), false, 'CDEF'); unset($visited[$cdef_id]); return null; } $parts[] = $item_name; } } unset($visited[$cdef_id]); $resolved = implode(',', $parts); if ($cache_bytes + strlen($resolved) <= 8388608) { $cache_bytes += strlen($resolved); $cache[$cdef_id] = $resolved; } return $resolved; } /** * Determines whether deleting a CDEF would leave a live reference behind. * * References owned by another CDEF in the same delete request are ignored. * Database errors fail closed so an uncertain dependency cannot be deleted. * * @param int $cdef_id CDEF being considered for deletion. * @param array $deleting_ids All CDEF IDs in the delete request. */ function cdef_is_in_use(int $cdef_id, array $deleting_ids = []) : bool { foreach (['graph_templates_item', 'aggregate_graph_templates_item', 'aggregate_graphs_graph_item'] as $table) { $references = db_fetch_assoc_prepared("SELECT cdef_id FROM $table WHERE cdef_id = ? LIMIT 1", [$cdef_id]); if ($references === false || cacti_sizeof($references) > 0) { return true; } } $parents = db_fetch_assoc_prepared('SELECT DISTINCT cdef_id FROM cdef_items WHERE type = 5 AND value = ?', [$cdef_id]); if ($parents === false) { return true; } $deleting = array_fill_keys(array_map('intval', $deleting_ids), true); foreach ($parents as $parent) { if (!isset($deleting[(int) $parent['cdef_id']])) { return true; } } return false; }