US Tray Menu 2: Defensive
US Tray Menu 2 : Introduction | Implementation | Language support | cmd problem | Defensive | Binaries
|
|
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.
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.
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.
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.
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.
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.
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 ===
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