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

WorldFallz’s picture

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

seancharles’s picture

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

WorldFallz’s picture

Firstly, why do you read deprecated forums ?
Presumably to catch fools like myself! :)

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