US Tray Menu 2: Defensive

 

UniServer 5-Nano
US Tray Menu 2.
UniServer 6-Carbo

US Tray Menu 2 Defensive code

Introduction

Our tray menu is dynamic providing real time user feedback. That said it is lacking a fundamental piece of user information! Can the servers be run on a user’s machine?

Although it is impossible to predict every eventuality any known issues that would prevent servers from running should be detected and a user informed accordingly.

This to a certain extent is defensive programming, attempting to run servers in an unpredictable environment may cause undesirable side effects.

It is of little use displaying an error number; if you have detected a cause provide useful feedback as to its remedy.

Initialisation checks

Prior to starting servers it is worth considering the following pre-checks ideally you would implement all of them.

Incorrect location

Strange as it may seem the first thing to perform is a correct installation check.

If extracted or copied to an incorrect location be draconian and refuse to start menu until corrective action is taken.

Top

Spaces in paths

Spaces in paths is a contentious issue, most modern programs will accept spaces however there are some useful legacy programs that will not. Uniform Serer imposes a restriction of no spaces in paths to folder UniServer. This allows any legacy code to be run in particular that used for some of its plugins.

Ideally to gain a small increase in performance Uniform Server should be installed at a disk top-level. For development that is not always convenient generally Uniform Server is installed in sub-folders. This is where a space issue comes to light, silently check for spaces if detected inform user and terminate script.

Note:

The above two scenarios are probably the only time you need to be draconian and refuse to start menu. All other situations may be corrected or resolved using menu items; hence start it. It provides a user quick access to error logs and ability to edit files.

Top

Ports in use

A fresh extraction of Uniform Server standard Apache and MySQL ports are used. A user may change these either manually or by running the move server script. In any event we know what values have been set. To run servers these ports must be free.

On detecting a port in use determine what is using it. Could be Skype, another server or even server that is performing the check. Inform user with information obtained.

Note: Server detected is current server, no need to inform user. This scenario occurs when tray menu is closed without shutting down servers these remain running either as a program or service.

Top

Passwords

For a test server generally there is no need to set any passwords.

That said we assume a user is going to use servers for production. It is important to set a MySQL password (default is root) hence at start-up nag user if it has not been changed.

Servers may be used to host other users it is important to set a password for Apanel. Otherwise servers are open to cross-site scripting allowing a user to set server passwords. We have no way of knowing how the servers will be used hence again nag admin user to set a password for Apanel.

Novice users may appreciate being nagged however this would drive me insane. Hence a configuration option to disable nagging would be a good idea especially for development.

A user should change above passwords at the first opportunity. Well if you are going to nag, that’s the first opportunity, present user with an option to change password.

If a user is going to ignore nagging and not change a password provide an option to disable nagging.

Top

Implementation

I am not going to provide full code if you are interested it can be found in file UniServer\unicon\tray_menu_2\start_unitray.php.

This section looks at code snippets.

Port in use - Manual check

To manually check a port in use type the following into a command prompt

  • netstat -anop TCP

It displays something similar to this:

C:\>netstat -anop TCP
Active Connections
  Proto  Local Address          Foreign Address        State           PID
  TCP    0.0.0.0:81             0.0.0.0:0              LISTENING       1068
  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:3307           0.0.0.0:0              LISTENING       3212
  TCP    127.0.0.1:1029         0.0.0.0:0              LISTENING       2884
  TCP    127.0.0.1:1037         127.0.0.1:1038         ESTABLISHED     3708
  TCP    192.168.1.6:1122       243.252.232.123:80     CLOSE_WAIT      3708
  • Scan down local address column for port number you are checking.
  • Example port 81 is displayed hence is in use.

Note:

To the right is the parent process PID (1068). This allows you to find parent process name.

Open "Task Manager" scan down list locate pid (e.g 1068) to the left (Column Image Name) is the parent process name.

Top

Port in use - Script

Understanding mechanics of a manual process allows us to convert it to a script.

  • We want to run the command netstat -anop tcp
  • Capture the result into an array $list

PHP function exec() is ideal for this, code as shown.

   $cmd = 'netstat -anop tcp'; // Command
   exec($cmd,$list,$return);   // Run cmd 

You can of course use a single line I prefer two, its just coding style.

PID's

Process identifiers (PID’s) are by definition unique first process created is assigned zero. Significance of this, in the above list you will never see a zero PID. Hence it can be used as a valid return value signifying port not found (not in use).

The following code:

 function find_port_pid($port){

   $cmd = 'netstat -anop tcp';                      // Command
   exec($cmd,$list,$return);                        // Run cmd 

   foreach($list as $text){                         // Scan array
    $pos = strpos($text, "TCP");                    // Look for TCP
      if ($pos !== false) {                         // String found
       $text = trim($text);                         // Remove L&R spaces
       $text = preg_replace('/\s+/', ' ', $text);   // Remove double spaces
       $str_array = explode(" ", $text);            // Create narray line split 
         $str_array2 = explode(":", $str_array[1]); // Create narray split to find portt 
           if($str_array2[1] == $port){             // Is it our port
             return $str_array[4];                  // Return pid
           }
      }
   }
   return 0 ; // Port not found return pid=0
}
  • Scans $list array looking for TCP these lines have a format of five columns
  • In preparation to blowing it apart each line has any excess white space removed
  • Line is blown apart at each space, creating an array $str_array of five elements corresponding to a column entry.
  • The second array element $str_array[1] is blown apart at character “:” to create a new array $str_array2 with two elements.
  • Second element $str_array2[1] of this array contains a port number.
  • This port number is compared to the port ($port) we are checking
  • On finding a match its corresponding pid $str_array[4] is returned.
  • If no match found above process is repeated for next line in array.
  • If array is exhausted a zero pid is returned.

Top

Port in use – Parent process

Having discovered a process is using a port we wish to use we need to find name of the offending process.

Code on the previous page provides a solution.

This code is converted into a suitable function, pass it a PID and its corresponding process name is returned.

Code shown below:

//=== PrintProcessNameAndID ===================================================
// Used by functions:
// is_process_running($proces_name)
// get_process_using_pid($pid)

    function PrintProcessNameAndID( $processID ){
       global $process_list;                         // Array List of processes
       global $pid_process_array;                    // Lis of pis processes

       $szProcessName = "<unknown>";
       $szProcessName = str_pad($szProcessName, MAX_PATH, "\0");
       
       $hProcess = Kernel32::OpenProcess (PROCESS_QUERY_INFORMATION |       
                                          PROCESS_VM_READ,
                                          false, $processID );
                                          
       if ( null != $hProcess ){
          $hMod = 0;
          $cbNeeded = 0;
          
          if (Psapi::EnumProcessModules( $hProcess, wb_get_address($hMod),
                \cStruct\HMODULE::sizeOf(), wb_get_address($cbNeeded) )){
             Psapi::GetModuleBaseName( $hProcess, $hMod, $szProcessName,
                                        strlen($szProcessName));
          }
          $szProcessName = \cStruct\rtrim($szProcessName);
          //echo "$szProcessName  (PID: $processID)\n";    // Original
          $process_list[] = $szProcessName;                // Create list of processes
          $pid_process_array[$processID] = $szProcessName; // Create list of keys(pids) processes
       }
    }
//=============================================== END PrintProcessNameAndID ===

//=== GET PROCESS USING PID ===================================================
function get_process_using_pid($pid){;

    global $pid_process_array;         // Array keys(pids) processes
    $pid_process_array = array(); 

    $aProcesses = new PROCESS_ARRAY();
    $cbNeeded = 0;

    if (! Psapi::EnumProcesses( $aProcesses, $aProcesses->sizeOf(), wb_get_address($cbNeeded))){
       echo "Enum Processes failed";
       exit;
    }

    $cProcesses = $cbNeeded / \cStruct\DWORD::sizeOf();  // use DWORD in the cStruct namespace to get the size of

    for ($i = 0; $i < $cProcesses; $i++){
       if( $aProcesses->aProcesses[$i] != 0){
          PrintProcessNameAndID( $aProcesses->aProcesses[$i] )   ;
       }
    }

     //print_r($pid_process_array);

     return $pid_process_array[$pid]; // Return process name

  }// end ia_proges_running

//=============================================== END GET PROCESS USING PID ===

Top

Summary

However due to a lack of interest for another plugin along with its continuing increase in size I decided to take the project to a more logical conclusion.

On the introduction page I referred to a final twist!

Remainder of this tutorial looks at integrating our new tray menu into Uniform Servers architecture. Next page covers binaries


Top