Unix-Specific OS Functionality


Although this is less true today than it was a number of years ago, PHP has always been considered a scripting language for Unix users. As such, a wide range of Unix-specific operating system level functions are available. These functions enable you to perform otherwise impossible tasks, such as direct input/output (I/O) (for accessing devices on the machine such as serial ports) and signal handling. We'll start off by looking at the capabilities PHP has for direct I/O.

Direct Input and Output (I/O)

Direct I/O functionality in PHP is generally used only when accessing hardware devices, such as serial ports, when the standard PHP file-access functions, such as fopen(), are insufficient. As such, unlike most other I/O operations, in PHP the resources used by the direct I/O extension are incompatible with those used elsewhere within PHP. In total, there are nine functions, which I will discuss in the sections that follow.

NOTE

To use the PHP Direct I/O capabilities, you must enable them by specifying the --enable-dio configure flag when PHP is compiled.


Opening and Closing a Direct I/O Connection

As is the case with any other input/output operation in PHP, to use any other functions the connection first must be opened. In terms of direct I/O, all file operations must begin with a call to the dio_open() function. The syntax for this function is as follows:

 dio_open($filename, $flags[, $mode]); 

$filename is a string representing the filename to open (such as /dev/modem), $flags is an integer bitmask representing the settings for this connection, and the final optional $mode parameter is the Unix mode by which to open the connection. Upon execution, dio_open() attempts to open the specified file and, if successful, returns a resource representing the connection or returns false on failure. For your reference, the following table shows possible flags to use for the $flags parameter.

Flags for the dio_open() $flags Parameter

* O_RDONLY

Open as read-only.

* O_RDWR

Open for read/write.

* O_WRONLY

Open as write-only.

O_APPEND

Open in append mode.

O_CREAT

Create the file if it doesn't exist.

O_EXCL

Fail if attempt to create a file through O_CREAT fails because the file previously existed.

O_NOCTTY

If the specified device filename is a terminal device, do not make the process the controller of the terminal.

O_NONBLOCK

Begin in nonblocking mode.

O_SYNC

Begin in Synchronous mode, forcing all writes to wait for the hardware to complete the action before continuing.

O_TRUNC

If the desired file already exists, truncate the file back to 0 bytes before continuing.


When selecting flags to use from the preceding table, it is important to note those that are marked with an asterisk character (*). One (and only one) of these three marked flags must be provided to use the dio_open() function. Additionally, if desired, one of these flags can be combined with any of the remaining flags to create a direct I/O connection of the appropriate behavior.

After a direct I/O connection has been established, it will be closed automatically upon the termination of the script. If you would like to terminate the connection sooner, you can close it manually using the dio_close() function:

 dio_close($dio_res); 

$dio_res is the direct I/O resource to close. An example of both the dio_open() and dio_close() functions is provided in the next section in Listing 22.1.

Reading Data from the Connection

After a connection has been opened, data can be read in a similar fashion to other PHP I/O functions. To read from an opened direct I/O connection, the dio_read() function is used. The syntax for the dio_read() function follows:

 dio_read($dio_res [, $length]); 

$dio_res represents the direct I/O resource returned from dio_open(), and the optional $length is an integer representing the number of bytes to read from the connection. If this parameter is not provided, dio_read() defaults to reading 1 kilobyte (1024 bytes).

In Listing 22.1, we will use the direct I/O functions to read 1 kilobyte of random data from the /dev/random device:

NOTE

The /dev/random device is a kernel-level device. As such, it may not be available in your particular operating system.


Listing 22.1. Basic Direct I/O Usage
 <?php      $dio = dio_open("/dev/random"     , O_RDONLY);      if(!$dio) {           die('Could not open /dev/random!');      }      /* Read 50 bytes from /dev/random */      $random_data = dio_read($dio, 50);      echo "Here is some random data (Hex Values): \n";      for($i = 0; $i < 50; $i++) {          printf("%X ", ord($random_data{$i}));      }      echo "\n";      dio_close($dio); ?> 

Writing Data to the Connection

Just as you can read from a direct I/O connection, you can write to it (assuming the connection is not read-only). To write to a direct I/O connection, PHP provides the dio_write() function; its syntax follows:

 dio_write($dio_res, $data [, $length]); 

$dio_res is the direct I/O connection resource, $data is the data to be written, and the optional parameter $length specifies the maximum length to write to the connection. Listing 22.2 uses the dio_write() function to write a string to the /dev/tty device (the active terminal):

Listing 22.2. Writing to a File Using Direct I/O
 <?php         $dio = dio_open("/dev/tty", O_RDWR | O_CREAT | O_TRUNC, 0777);         if(!$dio) {                 die("Could not open /dev/tty\n");         }         dio_write($dio, "Hello, World!\n");         dio_close($dio); ?> 

Along with the standard dio_write() function, PHP also provides the dio_truncate() function, which will truncate the specified file to a specified length. The syntax for the dio_truncate() function follows:

 dio_truncate($dio_res, $length); 

$dio_res is the direct I/O resource and $length is the length to truncate the file to.

NOTE

When specifying a truncation length, be aware of possible problems if the file is smaller than the specified length. Depending on the operating system, the file may be left untouched or padded with NULL characters to make up for the difference.


Adjusting the Direct I/O File Pointer

As with any read or write operation, a pointer keeps track of the location where the operation is taking place. Likewise, this pointer can be moved as necessary using the direct I/O API. The function to accomplish this task is the dio_seek() function, which has the following syntax:

 dio_seek($dio_res, $position [, $start]); 

$dio_res is the direct I/O connection resource. The next parameter, $position, is an integer representing the position to move the file pointer to relative to the optional $start parameter. The $start parameter is one of the following three options:

  • SEEK_SET Use the $position parameter as the literal location within the file.

  • SEEK_CUR Adjust the location $position bytes from the current location in the file.

  • SEEK_END Adjust the location $position bytes from the end of the file.

In the event that the $start parameter is not provided, the dio_seek() function will use the default value equivalent to the SEEK_SET constant. An example of using the dio_seek() function is shown in Listing 22.3.

NOTE

When you specify the $position parameter and SEEK_CUR or SEEK_END, a negative value may be used to indicate a location.


Listing 22.3. Writing to a File Using the dio_seek() function
 <?php         $dio = dio_open("/tmp/testing", O_RDWR | O_CREAT | O_TRUNC, 0777);         if(!$dio) {                 die("Could not open /tmp/testing\n");         }         dio_write($dio, "Hello, my name is Bill");         /* Back up 4 bytes */         dio_seek($dio, -4, SEEK_END);             /* Re-write */         dio_write($dio, "John");             dio_close($dio); ?> 

Retrieving Information About the Connection

The direct I/O extension can detail a wealth of information about a currently open connection by retrieving the stat information on it using the dio_stat() function. The syntax for this function is as follows:

 dio_stat($dio_res); 

$dio_res is the direct I/O resource to stat. When executed, this function returns an associative array containing the results of the stat or returns false on failure. For a complete description of all the values contained within the result of dio_stat(), refer to the PHP manual.

Configuring Your Direct I/O Connection

Thus far, I have introduced you only to the fundamentals of using the direct I/O capabilities of PHP. Being the low-level API that it is, a number of other functions are available to fine-tune your direct I/O connection. The first is the dio_fcntl() function, which allows you to perform a number of operations on an open direct I/O connection. The syntax of this function is as follows:

 dio_fcntl($dio_res, $command [, $args]); 

$dio_res is the direct I/O resource and $command is an integer specifying the operation to perform against the direct I/O connection. If necessary for the operation, the optional parameter $args is a mixed variable representing the specific arguments for the operation. A list of possible operations for the $command parameter is shown in the following table.

Possible Operations for dio_fcntl()

Command

Description

F_DUPFD

Find the lowest-numbered file descriptor greater than the one specified by the $args, make a copy of the specified direct IO connection, and return it.

F_GETLK

Get the status of the lock (if it exists) on the specified direct I/O connection.

F_SETFL

Set new optional flags for the connection (O_APPEND, O_NONBLOCK, O_ASYNC).

F_SETLK

Attempt to set or clear a lock on the direct I/O connection. Returns 1 if another process has the connection locked.

F_SETLKW

Identical to F_SETLK, except it will wait for the lock to be released automatically.


NOTE

For the F_SETFL flag, note the possibility of the O_ASYNC option. This option requires that PHP be compiled with support for process control, which is discussed later in this chapter.


When you use the dio_fcntl() function to set a lock, the optional $args parameter must be used to specify a number of arguments in the form of an associative array. The key names that must be specified and their meanings when setting a lock are provided in the following table:

The Structure of the dio_fcntl() Lock Array

Key Name

Description

start

The location within the file to begin the lock relative to the wenth key.

length

The size in bytes of the area of the file to lock from the start value. A value of 0 indicates locking of the remainder of the file.

type

The type of lock to create; possible values are F_RDLCK, F_WRLCK, and F_UNLCK, indicating the creation of a read lock, write lock, or the removal of the lock, respectively.

wenth

The location that identifies the meaning of the start offset value. Can be SEEK_SET, SEEK_CUR, or SEEK_END.


NOTE

It is important to note that when you use the F_GETLK operation for the dio_fcntl() function, the array returned is identical to that shown in the preceding table with an additional key pid representing the process ID with the lock (if any).


The example in Listing 22.4 uses the dio_fcntl() to dump the results of a lock check on a local file:

Listing 22.4. Using the dio_fcntl() Function
 <?php         $dio = dio_open("/tmp/testing", O_RDWR | O_CREAT | O_TRUNC, 0777);         if(!$dio) {             die("Could not open /tmp/testing\n");         }         $result = dio_fcntl($dio, F_GETLK);         echo "Lock Type: ";         switch($result['type']) {         case F_RDLCK:             echo "Read\n";             break;         case F_WRLCK:             echo "Write\n";             break;         case F_UNLCK:             echo "Not locked\n";             break;      }      echo "Lock exists from {$result['start']} for {$result['length']} bytes\n";      echo "Lock is controlled by PID {$result['pid']}\n\n";          dio_close($dio); ?> 

One of the possible uses for the direct I/O functionality within PHP is to access a terminal or another serial device from within a PHP script. However, to appropriately access such a device, a number of configuration options specifically for terminals must be properly set. To set these values, the dio_tcsetattr() function is provided with the following syntax:

 dio_tcsetattr($dio_res, $options); 

$dio_res is the direct I/O resource and $options is an associative array containing the options to set for the terminal connection. A list of the keys and their meanings for the $options array are shown in the following table:

Options for the dio_tcsetattr() function

baud

The baud rate. Possible values are 38400, 19200, 9600, 4800, 2400, 1800, 1200, 600, 300, 200, 150, 134, 110, 75, and 50. The default is 9600.

bits

The number of data bits. Possible values are 8, 7, 6, or 5. The default is 8 bits.

Parity

The number of parity bits. Possible values are 0, 1, or 2. The default is 0.

stop

The number of stop bits. Valid values are 1 or 2, with the default being 1.


PHP POSIX Functions

Since the days of PHP 3, there has been some measure of POSIX.1 (IEEE 1003.1) support, although back in those days the support was limited to only a handful of POSIX functions such as open(), read(), write(), and close(). In modern versions of PHP, the POSIX support has been extended to most (if not all) POSIX functions. As the title implies, this section is devoted to some of the more important POSIX functions.

Because of the broad scope of functionality POSIX.1 describes, this section will omit a significant portion of the available POSIX functions in PHP. For more detailed information regarding POSIX in PHP, consult the PHP manual at http://www.php.net/posix.

POSIX and Security in PHP

For those of you who are not entirely familiar with POSIX functions, be aware that they represent an incredible security risk to the unprepared Webmaster. With POSIX functions, a malicious user can retrieve information about users of your system and much more. As such, many functions in the POSIX extension for PHP require that PHP be running as a privileged (that is, root) user to be used.

Because security measures for POSIX functions are implied by the security provided by Unix-based operating systems themselves, PHP does not perform any measure of access protectioneven with safe mode enabled. Regardless, it is strongly recommended that these functions be disabled using the --disable-posix ./configure option when compiling PHP for an environment where they could be taken advantage of by a malicious user.

Knowing When Something Has Gone Wrong

When you work with the PHP POSIX functions, you will see that many return a Boolean value indicating simply the success or failure of the executed function. Because this return value is only marginally useful, let's start off our discussion of POSIX functions by looking at the way errors are handled. When a POSIX function is called and fails, an error code is generated that can be retrieved using the posix_get_last_error() function. As its name implies, this function will retrieve an integer representing the last POSIX error to occur or zero if no error has yet occurred. To receive a string description of the error based on this error code, the posix_strerror() function is provided with a syntax as follows:

 posix_strerror($error_code); 

$error_code represents the error code returned by the posix_get_last_error() function. Throughout the examples in this section, I will use these functions to display meaningful error messages in my sample scriptsstarting with user and group POSIX functions.

User and Group POSIX Retrieval Functions

One of the primary purposes of the PHP POSIX extension is to provide a number of functions that allow you to retrieve information about the user and group the current process belongs to. Through the POSIX extension, you can determine both the effective and real group/user ID for the current process (and, as you will see later, change them as desired). To begin, let's take a look at the user-related POSIX functions.

NOTE

Changing the effective or real user or group ID of a process is a privileged function requiring PHP to be running as a super user (that is, root).


As previously stated, the POSIX functions in PHP enable you to retrieve information about the effective and real user ID of the current process. To perform these actions in PHP, you can use two functions: posix_geteuid(), to retrieve the effective user ID of the process, and posix_getuid(), which retrieves the real user ID of the process. Neither of these functions requires any parameters and when executed, returns an integer representing the numeric user ID under which the current PHP process is being executed.

Although the numeric user ID is useful, it would be nice to be able to determine the actual username and other detailed information regarding the process owner. For these purposes, PHP provides the posix_getpwuid() function with the following syntax:

 posix_getpwuid($user_id); 

$user_id is the numeric ID of a user on the system (for instance, as returned by posix_getuid()). When executed, the posix_getpwuid() function returns an associative array containing the following information for the given user ID:

name

The short name associated with the ID

passwd

The encrypted password of the user

uid

The user ID

gid

The primary group ID of the user

gecos

Contact details for the user (see note)

dir

The home directory of the user

shell

The shell used by the user's account


NOTE

For the preceding list of array keys, be aware that the gecos key is hardly descriptive of the key's contents. It is unnecessary to get into the reasons for its name, but be aware that this key can contain a comma-separated list of details for the user in the following order:

  • User's full name

  • Office phone number

  • Office number

  • Home phone number

On most systems, everything but the full name of the user is omitted, thus the field is only of marginal value.


An example of using the preceding functions is shown in Listing 22.5:

Listing 22.5. Using POSIX to Retrieve User Information
 <?php         $uid = posix_getuid();         $euid = posix_geteuid();         $errcode = posix_get_last_error();         if($errcode != 0) {                 $errmsg = posix_strerror($errcode);                 die ("Error retrieving user info: $errmsg\n");         }         $uid_info = posix_getpwuid($uid);         $euid_info = posix_getpwuid($euid);          echo "The user executing this process is {$uid_info['name']}\n";         echo "The effective user is {$euid_info['name']}\n"; ?> 

An alternative to the posix_pwgid() function, which returns information about a user (by user ID), information can also be retrieved by a string username. This task is accomplished by the posix_pwnam() function using the following syntax:

 posix_pwname($username); 

$username is the username to retrieve information about. Other than the look-up method, this function behaves identically to its sister function posix_pwuid().

As is the case with users, working with groups using the POSIX functions in PHP is nearly identical. In fact, instead of using posix_getuid(), posix_geteuid(), posix_getpwnam(), and_getpwuid(), the functions posix_getgid(), posix_getegid(), posix_getgrnam(), and posix_getgrgid() are used, respectively. As was the case with the user-related functions, posix_getgid() and posix_getegid() return the real and effective user ID for the current process, respectively. In fact, the only real difference between the user and group functions are the details that can be retrieved. As previously stated, details regarding a particular group ID can be retrieved using the posix_getgrgid() function with the following syntax:

 posix_getgrgid($group_id); 

$group_id is the group ID to retrieve information about. When executed, this function returns an associative array describing the group ID provided or NULL on failure. The keys available within the return value are shown next:

name

The name of the group

passwd

The encrypted password for the group

members

An indexed array containing the user names of members of this group

gid

The group ID for this group


NOTE

As was the case between posix_getpwuid() and posix_getpwnam(), the only difference between posix_getgrgid() and posix_getgrnam() is the method of lookup. Each information retrieval function for groups returns an identical associative array.


An example of the use of these functions is provided in Listing 22.6:

Listing 22.6. Using Group-Related POSIX Functions
 <?php         $uid = posix_getuid();         $gid = posix_getgid();         $gid_info = posix_getgrgid($gid);         $uid_info = posix_getpwuid($uid);         $errcode = posix_get_last_error();         if($errcode != 0) {                 $errstr = posix_strerror($errcode);                 die("Could not retrieve information: $errstr\n");         }         echo "User {$uid_info['name']} belongs to group {$gid_info['name']}\n";         echo "The following is a list of other users in that group:\n\n";         foreach($gid_info['members'] as $uname) {                 echo "\t* $uname\n";         } ?> 

It may have occurred to the observant reader that thus far none of the functions I have discussed allows the developer to determine anything but the primary group of the executing process. Because a process can be a member of multiple groups, this would be a significant limitation indeed. Thankfully, PHP provides the posix_getgroups() function, which returns an indexed array of integers representing all the groups the current process belongs to. An example of the use of this function is shown in Listing 22.7.

Listing 22.7. Retrieve a Process Group List Using POSIX
 <?php         $groups = posix_getgroups();         $errcode = posix_get_last_error();         if($errcode != 0) {                 $errmsg = posix_strerror($errcode);                 die("Could not get group list: $errmsg\n");         }         echo "This process belongs to the following groups:\n\n";         foreach($groups as $group) {                 $gid_info = posix_getgrgid($group);                 echo "\t* {$gid_info['name']}\n";         } ?> 

Changing the Group or User

Now that we have discussed the majority of functions used to retrieve information regarding the effective user and group of the current process, let's take a look at those functions that allow you to change those values. To execute, these functions require PHP to be running as root (or similar) user.

NOTE

Although in the previous section I discussed user-related functions first, in this case I will begin with group-related functions. I have chosen this approach for a good reasonorder matters. As you will see, when changing the user or group ID of a process, the group ID should always be changed prior to the user ID.


Recall from the previous section that there are two types of groupseffective and real. Similarly, PHP provides two functions that allow you to modify the effective and real group that the current process runs as. These functions are posix_setegid() and posix_setgid(), respectively, with the following syntax:

 posix_setegid($group_id); posix_setgid($group_id); 

In both instances, $group_id is the new primary group ID for the group (either effective or real, depending on the function called). When executed, this function will return a Boolean value indicating whether the function succeeded.

Like the posix_setgid() function, which allows you to set the group ID of the current process (the executing PHP script), PHP also provides the posix_setuid() function, which allows you to set the user ID of the current process. The syntax for the posix_setuid() function has a syntax as follows:

 posix_setuid($user_id): 

$user_id is the user ID the current process should execute as. Upon success, this function will return a Boolean TRue or return false on failure. A similar function, posix_seteuid(), also exists, which sets the effective user ID of the process using the following syntax:

 posix_seteuid($user_id): 

Listing 22.8 shows an example of using the posix_setuid() function:

Listing 22.8. Changing the Effective User Using POSIX
 <?php         /* The username of the user we want to create            a file as */         $username = 'john';         /* Find the user ID of the specified user */         $uid_info = posix_getpwnam($username);         $errcode = posix_get_last_error();         if($errcode != 0) {                 $errstr = posix_strerror($errcode);                 die("Could not find user ID for '$username': $errstr\n");         }         $uid = $uid_info['uid'];         /* Change the user ID */         if(!posix_setuid($uid)) {                 $errcode = posix_get_last_error();                 $errstr = posix_strerror($errcode);                 die("Could not change the user ID: $errstr\n");         }         /* Create a temporary file name, and an empty file using            that name. */         $tmpname = tempnam("./", "PHP_HANDBOOK_");         touch($tmpname); ?> 

POSIX Process Functions

The fourth and final section in this chapter on POSIX functions discusses those functions related directly to the process. POSIX process control functions enable you to return information about the PHP process running your script, to send signals to other processes, and more. Of the four functions I'll discuss in this section, three are used to retrieve process information. Thus, we will discuss them first.

The first two functions are those that allow you to retrieve the process ID of the current PHP process and the parent of that process. To retrieve the process ID of the current PHP process, use the POSIX posix_getpid() function. Likewise, to retrieve the process ID of the parent of the current process, use the posix_getppid() function. Neither of these functions require any parameters, and each returns an integer representing the appropriate process ID as shown in Listing 22.9:

Listing 22.9. Retrieving the Current and Parent Process ID
 <?php         $pid = posix_getpid();         $ppid = posix_getppid();         echo "The current process ID is: $pid\n";         echo "The parent process ID is: $ppid\n"; ?> 

Similar to those functions that retrieve the process ID of the current or parent process, PHP also supports the capability to retrieve the process group identifier using the posix_getpgrp() function. This function accepts no parameters and, as expected, returns an integer representing the process group ID.

NOTE

For more information regarding process groups, consult a Unix reference or the man pages for the getpgrp(2) Unix command.


The fourth and perhaps most useful of the POSIX process functions is the posix_kill() function. Despite this function's name, the purpose of this function is to send signals to another process (which may not necessarily kill the process). The syntax for this function is as follows:

 posix_kill($process_id, $signal); 

$process_id is the process ID to send the signal to, and $signal represents the signal to send to the process. When you specify the $signal parameter, one of the signal constants must be used. If the PCNTL extension has been enabled in PHP, these constants are available as constants shown as follows:

SIGUP

Hangup or death of the controlling process

SIGINT

Interrupt from keyboard

SIGQUIT

Quit received from keyboard

SIGILL

Illegal instruction received

SIGABRT

Abort signal

SIGFPE

Floating point exception

SIGKILL

Process kill signal

SIGSEGV

Invalid memory reference

SIGPIPE

Broken pipe (write to pipe with no reader)

SIGALRM

Timer signal

SIGTERM

Termination signal

SIGUSR1

User-defined signal #1

SIGUSR2

User-defined signal #2

SIGCHLD

Child process stopped or terminated

SIGCONT

Continue signal (if stopped)

SIGSTOP

Stop process

SIGTSTP

Stop process (received from TTY)

SIGTTIN

TTY input for background process received

SIGTTOU

TTY output for background process


NOTE

Signals and signal handling are discussed only from a functional standpoint in this text. Detailed descriptions of using signals and their meanings within the Unix platform is a complex subject. For a detailed description, consult a Unix programming manual.


It is noteworthy to realize that, in the event PCNTL extension is not available in PHP, the posix_kill() function will still be available.

As an example of using the posix_kill() function, Listing 22.10 enters an infinite loop that waits 5 seconds before sending the SIGTERM (terminate) signal to itself, effectively terminating the process:

Listing 22.10. Sending a Signal Using posix_kill()
 <?php         /* This is necessary if PCNTL is not enabled */         if(!defined("SIGTERM")) {             define("SIGTERM", 15);         }         $pid = posix_getpid();         while(1) {                 sleep(5);                 /* Similar to using the 'exit' statement */                 posix_kill($pid, SIGTERM);         } ?> 

This function becomes particularly useful as we move into the next section of the chapter, where I discuss Unix process control. As you will see, certain signals can be caught and acted on from within PHP scripts.

Unix Process Control

Along with support for direct input and output in Unix and POSIX standards support, PHP also supports manipulation of the standard Unix signal model. Unix signals form one of the key foundations of the operating system, allowing the developer to perform actions such as stopping a process or forking the current process. In this section, we'll look at how to perform such tasks when running PHP in a Unix environment.

NOTE

Although this section of the chapter focuses on process control, it is beyond the scope of this book to discuss the details of Unix signal handling. Rather, it is recommended that those who do not have experience with Unix signals research the subject independently online or through a book dedicated to programming on the Unix platform.


Before I begin my discussion of the PHP Unix process control API, it is important to note that this extension should never be used in a Web environment. This extension is intended to be used only with the CLI version of PHP in a shell scripting environment with the PCNTL extension enabled (see Appendix A, "Installing PHP5 and MySQL," for more information).

Forking Processes in PHP

To begin the discussion of process control, we'll start by introducing how to fork a child process from within a PHP script. This process of forking a child is done using the pcntl_fork() function, which has the following syntax:

 pcntl_fork(); 

When this function is executed, a child process will be spawned and the parent pcntl_fork() function will return an integer representing the child process ID. For the child process pcntl_fork() returns NULL, allowing you to distinguish between the two processes. In the event PHP was unable to fork the process, pcntl_fork() will return 1. An example of using this function to fork a child process is shown in Listing 22.11:

Listing 22.11. Forking a PHP Script Using pcntl_fork()
 <?php         $child = pcntl_fork();         if($child == -1) {                 die ("Could not fork process.\n");         }         if($child) /* The parent process */ {                 for($i = 2; $i < 20; $i += 2) {                         echo "Parent: $i\n";                 }         } else /* The child process */ {                 for($i = 1; $i < 20; $i += 2) {                         echo "Child: $i\n";                 }         } ?> 

After the process has been forked, the child process can begin executing code independently of the parent that spawned it. To assist in the monitoring of this child process (most notably when the child process is complete), PHP provides the pcntl_waitpid() function. This function is used to suspend the execution of the current process until one of the following conditions are met:

  • The specified child process is terminated.

  • A signal is delivered terminating the current process.

The syntax for pcntl_waitpid() is as follows:

 pcntl_waitpid($pid, &$status, $options); 

$pid is the process ID returned from a pcntl_fork() function or one of the following alternative values:

$pid less than 1

Any child process whose process group ID is equal to the absolute value of $pid.

$pid equals 1

Wait for any child process.

$pid equals 0

Wait for a child process whose process group ID is equal to the current process group ID.


The second parameter $status is a pass-by-reference parameter used to store the status of the specified process when the pcntl_waitpid() function returns. This value can then be passed to other functions, which will be discussed shortly to ascertain the nature of the child process's termination.

The third and final parameter, $options, is a bit-mask parameter that allows you to further define the behavior of the pcntl_waitpid() function. Specifically, the $options parameter can be either zero or a combination of the following constants combined using the bitwise OR operator:

WNOHANG

Return immediately if no child has exited.

WUNtrACED

Return for children that are stopped and whose status has yet to be reported.


As the function name implies, the pcntl_waitpid() process will not return until the specified process(es) have terminated. Upon completion, the pcntl_waitpid() function returns the process ID of the terminated child, 1 on error, or zero if the WNOHANG option was specified and no child matching the specified process ID requirements was met. To demonstrate how the pcntl_waitpid() function can be of use, a simple example is provided below in Listing 22.12:

Listing 22.12. Child Management Using pcntl_waitpid()
 <?php         /* Fork two child processes */         $child1 = pcntl_fork();         /* How long each child should wait before            terminating */         $child1_delay = 5;         $child2_delay = 7;         if($child1 == -1) {                 die("Could not Fork first child\n");         }         if($child1) { /* Parent of Child 1 */                 $child2 = pcntl_fork();                 if($child2 == -1) {                         die("Could not Fork second child\n");                 }                 if($child2) { /* Parent of Child 2 and Child 1 */                         echo "Child 1 PID: $child1\n";                         echo "Child 2 PID: $child2\n";                         echo "Waiting on children..\n";                         do {                                 $child_term = pcntl_waitpid(0, $status,                                                              WNOHANG);                            } while(!$child_term);                         echo "Process $child_term finished first\n";                 } else { /* Second child */                         sleep($child2_delay);                         exit;                 }         } else { /* First child */                 sleep($child1_delay);                 exit;         } ?> 

As previously noted, the pcntl_waitpid() function requires the second pass-by-reference $status parameter. This parameter is populated with the current status of the specified process when the pcntl_waitpid() function is finished. To determine the meaning of this parameter, a family of functions returns further information from that status variable. Those functions and their meaning are as follows:

pcntl_wexitstatus($status);

Returns the integer exit code of the child process.

pcntl_ifexited($status);

Returns a Boolean indicating whether the child exited successfully.

pcntl_ifsignaled($status);

Returns a Boolean indicating whether the child exited because of a signal it received.

pcntl_ifstopped($status);

Returns a Boolean indicating whether the child is currently stopped.

pcntl_wstopsig($status):

Returns the signal that the child received that caused it to stop. Used only if pcntl_ifstopped() returns TRue.


Catching Signals Sent to Processes

In the previous section, I discussed the POSIX functions available to PHP scripts, including the posix_kill() function. As you recall, the posix_kill() function is used to send signals to another Unix process, which is then acted upon. What I have not discussed in any detail, however, is how these signals can be caught from within your PHP scripts and then acted upon in any way you see fit.

To catch signals sent to a particular script, the process must register a signal handler. This signal handler is a callback function that will be called when a signal is received, giving your script an opportunity to act on this signal. To register a signal handler from within PHP, let's introduce the pcntl_signal() function with a syntax as follows:

 pcntl_signal($signal, $handler [, $restart]); 

$signal is the signal to catch and $handler is a string representing the PHP function to call when the specified signal is received. The third optional parameter, $restart, is a Boolean indicating whether system-call restarting should be used when the signal arrives (the default is TRue). When executed, this function attempts to register the signal callback and return a Boolean value indicating whether the callback was registered successfully.

NOTE

When registering a signal handler, note that the handler will apply to the process executing the function call. Thus, if called from a forked child, it will be registered for that child only.


When registering a signal handler, one important detail that is often neglected is the use of the declare statement. This general-use statement allows you to define how often PHP will check for a signal in terms of executed operations (or ticks). For instance, to instruct PHP to check for a signal and execute the callback every three operations, the following declare statement would be used:

 <?php declare(ticks = 3); ?> 

Failure to properly declare a ticks statement in your PHP script will result in signal callbacks not being called as expected.

As previously explained, when a signal is received that has been designated to be caught using the pcntl_signal() function, the registered function will be called and passed a signal parameter (the signal that was sent). In this function, you are free to execute any code you see fit. After your function has returned, the signal will then be processed in the normal fashion by PHP itself. Thus, if your script receives a SIGTERM signal, although you have an opportunity to act on the signal, you cannot prevent the script from terminating after the callback function returns.

A basic example of signal handling from within PHP can be found in Listing 22.13:

Listing 22.13. Registering a Simple Signal Handler
 <?php         declare(ticks=1);         function signal_handler($signal) {                 if($signal == SIGUSR1) {                         echo "You hit it! Terminating...\n";                         exit;                 } else {                         echo "Didn't know what signal $signal was..\n";                 }         }         pcntl_signal(SIGUSR1, "signal_handler");         $pid = posix_getpid();         while(1) {                 sleep(5);                 if(rand(1, 100) > 50) {                         posix_kill($pid, SIGUSR1);                 } else {                         echo "Whoops, another 5 second wait...\n";                 }         } ?> 

To illustrate the use of a more complex signal handler (one using multiple signals and forked processes), see Listing 22.14. In this script, PHP is forked using the pcntl_fork() command and a signal handler is registered for each process. These two processes then send messages back and forth to each other, displaying the signal received.

Listing 22.14. An Advanced Signal/Fork Example
 <?php         declare(ticks = 1);      /* define string representations for signals */         $signals = array(SIGHUP => "SIGHUP",  SIGINT => "SIGINT",                          SIGQUIT => "SIGQUIT", SIGILL => "SIGILL",                          SIGTRAP => "SIGTRAP",  SIGABRT => "SIGABRT",                          SIGIOT => "SIGIOT", SIGBUS => "SIGBUS",                          SIGFPE => "SIGFPE", SIGPROF => "SIGPROF",                          SIGUSR1 => "SIGUSR1", SIGSEGV => "SIGSEGV",                          SIGUSR2 => "SIGUSR2", SIGPIPE => "SIGPIPE",                          SIGALRM => "SIGALRM", SIGTERM => "SIGTERM",                          SIGSTKFLT => "SIGSTKFLT", SIGCLD => "SIGCLD",                          SIGTTIN => "SIGTTIN", SIGTTOU => "SIGTTOUT",                           SIGURG => "SIGURG", SIGXCPU => "SIGXCPU",                           SIGXFSZ => "SIGXFSZ", SIGVTALRM => "SIGVTALRM",                           SIGWINCH => "SIGWINCH", SIGPOLL => "SIGPOLL",                          SIGIO => "SIGIO", SIGPWR => "SIGPWR",                          SIGSYS => "SIGSYS", SIGBABY => "SIGBABY");      /* The parent callback signal handler */         function parent_signal_handler($signal_id) {                 global $signals;                 $pid = posix_getpid();            $time = date("h:i:s");                 echo "$time: Parent Received {$signals[$signal_id]}..\n";         }      /* The child callback signal handler */         function child_signal_handler($signal_id) {                 global $signals;                 $ppid = posix_getppid();            $time = date("h:i:s");                 echo "$time: Child Processing signal " .                       "{$signals[$signal_id]}..\n";                     /* Send the SIGUSR1 signal to the parent if the                    child process is sent the SIGTERM or SIGUSR1 signal */                 switch ($signal_id) {                         case SIGTERM:                         case SIGUSR1:                                 echo "Terminating Application..\n";                                 posix_kill($ppid, SIGUSR1);                                 exit;                 }         }      /* Fork the process */         $child = pcntl_fork();         if($child == -1) {                 die("Could not fork child process.");         }         if($child) { /* This is the parent process */                 $pid = posix_getpid();            /* Register a signal handler for all the signals */                 foreach($signals as $sig => $sig_str) {                         @pcntl_signal($sig, "parent_signal_handler");                 }           /* Wait for 5 seconds, then send the SIGUSR2 signal to                   the child process */                 sleep(5);                 posix_kill($child, SIGUSR2);            /* Wait 5 more seconds and then send the  SIGUSR1 signal                    to the child process */                 sleep(5);                 posix_kill($child, SIGUSR1);            /* Wait for the child process to terminate */                 pcntl_waitpid($child, $status);                 echo "Parent Exiting parent process.\n";         } else { /* This is the child process */            /* Register signal callbacks for the child */                 foreach($signals as $sig => $sig_str) {                         @pcntl_signal($sig, "child_signal_handler");                 }                 $ppid = posix_getppid();                     /* Send one SIGUSR2 signal every three                         seconds to the parent */                 while(1) {                         sleep(3);                         posix_kill($ppid, SIGUSR2);                 }         } ?> 

When this script is executed, the output will be as follows:

 05:41:05: Child Processing signal SIGUSR2.. 05:41:08: Child Processing signal SIGUSR1.. Terminating Application.. 05:41:02: Parent Received SIGUSR2.. 05:41:05: Parent Received SIGUSR2.. 05:41:05: Parent Received SIGUSR2.. 05:41:08: Parent Received SIGUSR2.. 05:41:08: Parent Received SIGUSR1.. 05:41:08: Parent Received SIGCLD.. Parent Exiting parent process. 

NOTE

Because of the nature of forked applications, the order in which the messages in Listing 22.14 appear may vary.


As shown, when this script is executed, it forks into a parent and child process. By this output, it appears that the child process received both of its signals prior to the parent receiving any of its signals. However, as can be proved by the timestamps on each line, the order of signals is as expected.

Setting an Alarm Signal

Now that you are familiar with sending and catching signals between processes, let's take a look at another useful functionthe pcntl_alarm() function. This function is designed to trigger the transmission of the SIGALRM signal after a specified amount of time. The syntax for this function is as follows:

 pcntl_alarm([$seconds]); 

The optional $seconds parameter represents the amount of time in seconds before the SIGALRM signal will be sent to the current process. When executed, this function returns the number of seconds before a previously set alarm was to be triggered.

When using the pcntl_alarm() function, it is important to note that only one alarm can be set at a time. If an alarm has already been set and pcntl_alarm() is called again, the previously set alarm is canceled. If pcntl_alarm() is called without any parameters, pcntl_alarm() will not set a new alarm and returns only the amount of time before the next alarm is to be sent. Listing 22.15 illustrates the use of this function:

Listing 22.15. Setting Alarms Using pcntl_alarm()
 <?php         ob_end_flush();         declare(ticks = 1);         function alarm() {                 echo "BRRRRIIIINGGGGGG!!!\n";                 pcntl_alarm(3);         }         pcntl_signal(SIGALRM, "alarm");         pcntl_alarm(2);         for($i = 1; $i <= 10; $i++) {                 echo "Tick ($i)...";                 sleep(1);         } ?> 

In this function, we set a signal handler for the SIGALRM signal and then set an alarm to be set to go off in two seconds. Then, we display a message while we wait every second. After two seconds, the alarm is triggered and the alarm() function is called. From within this callback function, another alarm is sent (this time for three seconds). This process continues until 10 seconds has passed. The output of this script is as follows:

 Tick (1)...Tick (2)...BRRRRIIIINGGGGGG!!! Tick (3)...Tick (4)...Tick (5)...BRRRRIIIINGGGGGG!!! Tick (6)...Tick (7)...Tick (8)...BRRRRIIIINGGGGGG!!! Tick (9)...Tick (10)... 

Jumping Processes in PHP

The final process control function we will look at is the pcntl_exec() function. This function is used to completely transfer control of the current process (including the user ID, group ID, and other permissions) to another application, thereby terminating PHP. The syntax for this function is as follows:

 pcntl_exec($exec [, $args [, $envs]]); 

$exec is the application to execute. The first optional parameter $args represents an indexed array of command-line arguments to pass to the executed application, and the second optional $envs parameter is an associative array containing key/value pairs of environment variables to set for the application. Upon success, PHP is terminated and the called application is executed. Upon failure, pcntl_exec() returns a Boolean false.



PHP 5 Unleashed
PHP 5 Unleashed
ISBN: 067232511X
EAN: 2147483647
Year: 2004
Pages: 257

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net