Hi,
Today I had a very nasty 'shock' whilst developing a module for 6.2 and it took some time to figure out what had happened. To a newbie module developer this could be a catastrophic incident because the cause is not that obvious and the solution even less so.
SYMPTOM:
All menu routing and page requests result in a page not found error after the fault takes place.
CAUSE:
In the menu_router_build() function the DELETE statement is executed *before* the new set of callbacks have been produced, this means that a 'malicious' module could wipe out the entire menu router table and completely disable the operation of a site.
SUGGESTED RECOVERY:
Re-import the menu_router table from an SQL backup or a default installation which should restore the administrative functionality allowing further restorative actions to be undertaken.
HOW TO RE-CREATE:
Put a die statement in your hook_menu() implementation. I did this very innocently to see what was going on as I had some issues with what I was doing at the time.
SUGGESTED FIX / IMPROVEMENT:
The current implementation of menu_router_build() is as follows:
/**
* Collect, alter and store the menu definitions.
*/
function menu_router_build($reset = FALSE) {
static $menu;
if (!isset($menu) || $reset) {
if (!$reset && ($cache = cache_get('router:', 'cache_menu')) && isset($cache->data)) {
$menu = $cache->data;
}
else {
db_query('DELETE FROM {menu_router}');
// We need to manually call each module so that we can know which module
// a given item came from.
$callbacks = array();
foreach (module_implements('menu') as $module) {
$router_items = call_user_func($module .'_menu');
if (isset($router_items) && is_array($router_items)) {
foreach (array_keys($router_items) as $path) {
$router_items[$path]['module'] = $module;
}
$callbacks = array_merge($callbacks, $router_items);
}
}
// Alter the menu as defined in modules, keys are like user/%user.
drupal_alter('menu', $callbacks);
$menu = _menu_router_build($callbacks);
cache_set('router:', $menu, 'cache_menu');
}
}
return $menu;
}
You can see that the DELETE FROM takes place before the new callback structure has been composed which means that anything that causes this loop to break / die / terminate prematurely (i.e. an unstoppable "die" or un-recoverable error) will leave the table totally empty and any future page requests result in a page not found response.
Therefore I suggest that the DELETE FROM be moved after the loop, when the new callback data is available for updating the database.
I have tested this change on my development system and putting a 'die' statement in the hook_menu() function no longer disables the system. I am leaving this change in !
/**
* Collect, alter and store the menu definitions.
*/
function menu_router_build($reset = FALSE) {
static $menu;
if (!isset($menu) || $reset) {
if (!$reset && ($cache = cache_get('router:', 'cache_menu')) && isset($cache->data)) {
$menu = $cache->data;
}
else {
// We need to manually call each module so that we can know which module
// a given item came from.
$callbacks = array();
foreach (module_implements('menu') as $module) {
$router_items = call_user_func($module .'_menu');
if (isset($router_items) && is_array($router_items)) {
foreach (array_keys($router_items) as $path) {
$router_items[$path]['module'] = $module;
}
$callbacks = array_merge($callbacks, $router_items);
}
}
// Alter the menu as defined in modules, keys are like user/%user.
drupal_alter('menu', $callbacks);
// SUGGESTED CHANGE POINT: =>
// New data available: delete old routing information now.
db_query('DELETE FROM {menu_router}');
// END CHANGE-POINT
$menu = _menu_router_build($callbacks);
cache_set('router:', $menu, 'cache_menu');
}
}
return $menu;
}
For completeness I have added the debug_stacktrace output so you can see how it got to where it did from the root controller, index.php:
Array
(
[0] => Array
(
[function] => ppvadmin_menu
[args] => Array
(
)
)
[1] => Array
(
[file] => /Library/WebServer/Documents/includes/menu.inc
[line] => 1666
[function] => call_user_func
[args] => Array
(
[0] => ppvadmin_menu
)
)
[2] => Array
(
[file] => /Library/WebServer/Documents/includes/menu.inc
[line] => 1641
[function] => menu_router_build
[args] => Array
(
[0] => 1
)
)
[3] => Array
(
[file] => /Library/WebServer/Documents/modules/system/system.admin.inc
[line] => 623
[function] => menu_rebuild
[args] => Array
(
)
)
[4] => Array
(
[function] => system_modules
[args] => Array
(
[0] => Array
(
[storage] =>
[submitted] =>
[post] => Array
(
)
)
)
)
[5] => Array
(
[file] => /Library/WebServer/Documents/includes/form.inc
[line] => 358
[function] => call_user_func_array
[args] => Array
(
[0] => system_modules
[1] => Array
(
[0] => Array
(
[storage] =>
[submitted] =>
[post] => Array
(
)
)
)
)
)
[6] => Array
(
[function] => drupal_retrieve_form
[args] => Array
(
[0] => system_modules
[1] => Array
(
[storage] =>
[submitted] =>
[post] => Array
(
)
)
)
)
[7] => Array
(
[file] => /Library/WebServer/Documents/includes/form.inc
[line] => 102
[function] => call_user_func_array
[args] => Array
(
[0] => drupal_retrieve_form
[1] => Array
(
[0] => system_modules
[1] => Array
(
[storage] =>
[submitted] =>
[post] => Array
(
)
)
)
)
)
[8] => Array
(
[function] => drupal_get_form
[args] => Array
(
[0] => system_modules
)
)
[9] => Array
(
[file] => /Library/WebServer/Documents/includes/menu.inc
[line] => 346
[function] => call_user_func_array
[args] => Array
(
[0] => drupal_get_form
[1] => Array
(
[0] => system_modules
)
)
)
[10] => Array
(
[file] => /Library/WebServer/Documents/index.php
[line] => 18
[function] => menu_execute_active_handler
[args] => Array
(
)
)
)
Comments
Why post this in a forum
Why post this in a forum group mark "deprecated"??? You should post this in the issue queue for drupal core v6 with a component of "menu system" at : http://drupal.org/node/add/project_issue/drupal/bug
===
Give a man a fish and you feed him for a day. Teach a man to fish and you feed him for a lifetime. -- Lao Tzu
"God helps those who help themselves." -- Benjamin Franklin
"Search is your best friend." -- Worldfallz
Firstly, why do you read
Firstly, why do you read deprecated forums ?
Presumably to catch fools like myself! :)
Secondly, the Initial thick blue header on the main forum page does not bely the indenting; I thought the 'Deprecated Forums' section was in fact empty, so I just didn't see that the forum I posted to was deprecated.
Thirdly, I will stuff it in the issue queue.
Thanks
:)
Sean
Firstly, why do you read
LOL, actually i don't-- I followed a link here you posted in another thread. And even thought that post wasn't in a depracated forum, there's very little chance the right people would see it in the forums whereas in the issue queue it can get the attention it deserves.
I wasn't trying to be a smart @ass-- this looks like an important issue that should be brought to the attention of the developers asap, and they wouldn't see it here.
===
Give a man a fish and you feed him for a day. Teach a man to fish and you feed him for a lifetime. -- Lao Tzu
"God helps those who help themselves." -- Benjamin Franklin
"Search is your best friend." -- Worldfallz