Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Perl 5 Unleashed

Chapter 15 -- Direct Access to System Facilities

Chapter 15

Direct Access to System Facilities


CONTENTS


Perl can be used to access system facilities not directly available in shell or awk programs. This chapter discusses some of the ways in which Perl programs can access system files and resources. The bulk of this chapter provides information on accessing data structures within system files on a UNIX system. In Chapter 9, "Portability Issues with Windows and Perl," and Chapter 10, "Win32 Modules on Windows NT," I discussed ways of accessing files on an NT system when covering portability issues.

Introduction

Accessing system facilities enables you to add that extra spice to your Perl programs. You have already seen how to call UNIX programs with the back quote (`) operators or the system() call. Both these mechanisms are costly in terms of using system resources because they start a subshell to execute another process. Perl offers many utilities to access system files and affects process parameters (such as process priorities) without resorting to firing up another process to perform a simple command.

Let's start with an example that shows you how to get more information about the user running your Perl program. Sometimes it might be nice to personalize your Perl scripts by sending messages back to the user running your script. It's easy to derive the user name from the /etc/passwd file or from calling the getlogin() function. Here's the syntax for the getlogin() function:

$userName = getlogin();

$userName is the returned user ID. For example, you could write a script to get the name and print out an appropriate greeting based on the user name:

$logname = getlogin();
if ($logname == "khusain") {
        print ("Hello, Kamran! How are you?\n");
} else {
if ($logname == "mgr2") {
        print ("Oh no, it's you again !! !!!! !!\n");
}

I am sure that you can come up with more practical applications rather than spewing greetings and salutations based on a user name. Practical examples include getting files, mail, and so on based on the user name. A number of utilities are available that do such things as answer your mail while you're on vacation and manage your mail from multiple sources. Check out the archives at the various Perl archive sites listed in appendix B, "Perl Module Archives," for these files. Most of these utilities are copyrighted by their authors, so they cannot be printed here, but you will get a good idea of what they can do.

Working with UNIX Users and Groups

Perl is great for working with user and group information files in the /etc directory. You can get user and group names, IDs, and other useful information from within your Perl programs to create powerful system administration applications.

Information from /etc/passwd with getpwent()

The getpwent() function enables you to access sequentially entries in the /etc/passwd file. Here's the syntax for the getpwent function:

($username,
$password,
$userid,
$groupid,
$quota,
$comment,
$userInfo,
$userHome,
$loginShell) = getpwent();

Here are the returned values from the getpwent call:

$username Contains the login name of the user
$password Contains the user's encrypted password
$userid The user ID
$groupid The group ID
$quota System dependent and may not exist
$comment System dependent and may not exist
$userInfo Personal information about the user
$userHome The user's home directory
$loginShell The user's startup command shell

To access each entry of the password file in turn, you can use the getpwent() function. Calling the getpwent() function for the first time in a program returns the first item in the /etc/passwd file. Every subsequent call to getpwent()returns the next entry in the file. The function returns an empty list after reaching the last entry in the file. Calling the getpwent() function after an empty list is received returns the first item in /etc/passwd, and you can start over. The getpwent() function has two related functions: setpwent() to rewind the file and endpwent() to close the /etc/passwd file. Here's the syntax for the setpwent function:

setpwent (keepopen);

If keepopen is non-zero, the /etc/passwd file is left open for reading and any previously cached information about the file is kept in memory. If keepopen is set to zero, any cached information in memory is flushed and the file is read again with the first entry available for a call to getpwent(). The endpwent function simply closes the /etc/password file.

Listing 15.1 uses getpwent to list the user names known by the machine as well as their user IDs.


Listing 15.1. Listing password entries.
 1 #!/usr/bin/perl
 2
 3  while (1) {
 4          last unless (($username, $password, $userid)
 5                        = getpwent());
 6          $users{$username} = $userid;
 7  }
 8  print ("Users on this machine:\n");
 9
10  foreach $user (sort keys (%users)) {
11          printf ("%-20s %d\n", $user, $users{$user});
12  }

The while loop in this listing calls getpwent() to read every entry in the /etc/password file. For this script, we are using only the first three elements of the returned list: the users' names, their encrypted passwords, and their IDs. The values are stored in the %users associative array and displayed in the for() loop. The output should look like this:

Users on this machine:
adm                  3
bin                  1
daemon               2
ftp                  404
games                12
guest                405
halt                 7
khusain              501
lp                   4
mail                 8
man                  13
news                 9
nobody               65535
operator             11
postmaster           14
ppp                  504
root                 0
shutdown             6
sync                 5
tparker              503
uucp                 10
uzma                 505
walter               502

Two sister functions exist for getting the same nine-item list about a user in the /etc/passwd file: getpwnam() and getpwuid(). The items in the returned list are in the same type as those returned by a call to getpwent(). The getpwnam() function returns the list given a user name, whereas the getpwuid() function returns the list given a user ID.

The getpwnam() and getpwuid() functions have the following syntax:

($username, $password, $userid, $groupid,
$quota, $comment, $userInfo, $userHome, $loginShell
)
    = getpwnam ($name);

($username, $password, $userid, $groupid,
$quota, $comment, $userInfo, $userHome, $loginShell)
    = getpwuid ($id);

An empty list is returned if no matching entry is found in /etc/passwd. A common use for getpwnam is to get a user ID for a user in a Perl script and use that value for creating temporary files.

Getting Group-Related Information with getgrent() and getgrnam()

The getgrent function is used to list the contents of an entry in the /etc/group file. The following information is provided for each entry:

Here's the syntax for the getgrent function:

($gname, $gpasswd, $gid, $gmembers) = getgrent;

This function returns four items corresponding to the previous list. The name, password, and ID fields are all scalar values. The value of gmembers is a list of user IDs separated by spaces.

The setgrent function sets the pointer in the /etc/group file back to the top. After setgrent is called, the next call to getgrent retrieves the first element of the /etc/group file. The endgrent function stops further access to the elements in the /etc/group file and frees up the memory used to store group information.

Here's the syntax for these functions:

setgrent();
endgrent();

Each call to getgrent returns one line from the /etc/group file. A NULL value (that is, an empty list) is returned when the last item is read. To print the contents of the group file, use a while loop like the one shown in Listing 15.2.


Listing 15.2. Getting group information.
1 while (($gname, $gpasswd, $gid, $gmembers) = getgrent) {
2         $groupsFound{$gname} = $gmembers;
3 }
4 foreach $i (sort keys (%groupsFound)) {
5     print "\n User IDs for group:", $groupsFound{$i} ;
6 }

The getgrnam function returns an /etc/group file entry when given a group name. Here's the syntax for the getgrnam function:

($gname, $gpasswd, $gid, $gmembers) = getgrnam ($name);

The variable $gname is the group name to search for. The $getgrnam returns the same four-element list that getgrent returns. Here is the output:

adm                  4
bin                  1
daemon               2
disk                 6
floppy               11
kmem                 9
lp                   7
mail                 12
man                  15
mem                  8
news                 13
nogroup              65535
root                 0
sys                  3
tty                  5
users                100
uucp                 14
wheel                10

Here's another sample of how to list users given a group name. Listing 15.3 shows a simple script that prints the users in a group.


Listing 15.3. A program that uses getgrnam.
 1 #!/usr/bin/perl
 2 print ("Please enter name of the group:\n");
 3 $name = <STDIN>;
 4 chop($name); #
 5
 6 if (!(($gname, $gpasswd, $gid, $gmembers) = getgrnam ($name))) {
 7            die ("There is no  $name group!. \n");
 8 }
 9
10 $count = 0;
11 while (1) {
12         last if ($gmembers eq "");
13         ($uid, $gmembers) = split (/\s+/, $gmembers, 2);
14         printf ("  %-15s", $uid);
15         $count++;
16         if (($count % 3) == 0) {
17                 print ("\n");
18         }
19 }
20 if ($count % 4) {   # finish it off.
21         print ("\n");
22 }

Lines 16 and 20 print the output four items per line. getgrid() retrieves the user information as returned by getgrnam(), except that it retrieves it by group ID. Here's the syntax for the getgrid function:

($gname, $gpasswd, $gid, $gmembers) = getgrid ($gid);

Generally, the call is just used to get the group name given a group ID:

($gname) = getgrid (3);

Be careful, though, to parenthesize the $gname variable to indicate that the $gname variable is an element in a list and not a list itself ! If you make the call like this:

$gname = getgrid (3);

the value of $gname is the returned list, not the first element of the array.

Getting Information in Network Files

Perl offers several functions to get information about networking files and items in the files on your system. By using these functions, you can create very powerful networking applications.

The getnetent Function

The getnetent function enables you to read entries in the /etc/networks file for all the names and addresses recognized as valid names by the domain name server for your machine. Here's the syntax for getnetent():

($name, $aliases, $addrType, $inet) = getnetent();

Four items are returned by this function:

Listing 15.4 shows how you can use getnetent to list the machine names and addresses at your site.


Listing 15.4. A program that uses getnetent.
1 #!/usr/bin/perl
2 print ("Networks on this machine:\n");
3 while (($name, $aliases, $atype, $inet) = getnetent()) {
4         ($a,$b,$c,$d) = unpack ("cccc", $inet);
5         print "$name = ";
6      printf " $ %d %d %d %d \n",$a,$b,$c,$d;
7 }

Each iteration in the while reads one entry in the /etc/networks file. If the last entry in the /etc/networks file has been read, the getnetent function returns an empty list and the while loop terminates. Each non-empty entry read is assigned to the variables $name, $aliases, $atype, and $inet.

The getnetbyaddr function returns the next available entry from /etc/networks with a given network number. Here's the syntax for the getnetbyaddr function:

($name, $aliases, $atype, $inet) = getnetbyaddr ($inaddr, $itype);

The getnetbyaddr() function returns the same types of values as the getnetent() function. The input parameters to getnetbyaddr() differ from the getnetent() function. The $inaddr is the network number that you are looking for. The $inaddr value must be a packed four-byte integer whose four bytes are the A, B, C, and D components of an Internet address. Use the pack command to create the $inet word:

@bytes = (204,251,103,2);
$inaddr = pack ("C4", @bytes);

The itype variable is almost always set to &AF_INET for Perl scripts on UNIX systems.

The getnetbyname() function is just like the getnetbyaddr() function except that it takes a network name (or alias) instead of an address. The returned values for an entry in the /etc/networks file are the same, too. Here's the syntax for the getnetbyname function:

($name, $aliases, $atype, $inet) = getnetbyname ($networkName);

The setnetent and endnetent functions in Perl rewind and close the /etc/networks file for access. The setnetent function rewinds the /etc/networks file. After a call to setnetent(), the next getnetent() call returns the first item in the /etc/networks file. Here's the syntax for the setnetent function:

setnetent (keepopen);

If keepopen is non-zero, the /etc/networks file is left open for reading, and any previously cached information about the file is kept in memory. If keepopen is set to zero, any cached information in memory is flushed and the file is read again with the first entry available for a call to getnetent(). The endnetent() function accepts no parameters and simply closes the /etc/networks file.

Working with Host Names Using gethostbyaddr() Functions

The gethostbyaddr() function accesses the /etc/hosts file for the host name given a particular Internet address. Here's the syntax for the gethostbyaddr function:

($name, $aliases, $addrtype, $len, $addr)
    = gethostbyaddr ($inaddr, $atype);

This function needs two arguments:

The Internet address is in the packed form as in the getnetaddr() call. The $inaddr value must be a packed four-byte integer whose four bytes are the A, B, C, and D components of an Internet address. Use the pack command to create the $inet word:

@bytes = (204,251,103,2);
$inaddr = pack ("C4", @bytes);

The gethostbyaddr function returns a list with five items in it:

Listing 15.5 shows how you can use gethostbyaddr to retrieve the Internet address corresponding to a particular machine name.


Listing 15.5. A program that uses gethostbyaddr.
 1 #!/usr/bin/perl
 2
 3 print ("Enter the Internet address to look for :\n");
 4 $machine = <STDIN>;
 5 $machine =~ s/^\s+|\s+$//g; # remove whitespaces around it
 6
 7 @bytes = split (/\./, $machine);
 8
 9 $packaddr = pack ("C4", @bytes);
10
11 if (!(($host, $aliases, $addrtype, $len, @addrlist) =
12         gethostbyaddr ($packaddr, &AF_INET))) {
13         die ("No such $machine was found.\n");
14 }
15
16 if ($aliases ne "") {  # i.e. you have more than one alias
17         print ("$host: Aliases for $host are :\n");
18         @alternates = split (/\s+/, $aliases);
19         for ($i = 0; $i < @alternates; $i++) {
20                 printf "%d:  %s \n",$i, $alternates[$i];
21         }
22 }
23 else {
24 print " This $host has no aliases ";
25 }

The following is sample output for a machine using the script called 15_6.pl:

$ 15_6.pl
Enter the  Internet address to look for :
204.222.245.10
pop.ikra.comAliases for pop.ikra.com are :
0:  pop
1:  www.ikra.com

You can get the host information by specifying the name to the gethostbyname function. The gethostbyname function is like gethostbyaddr, except it uses a name instead of an address. Here's the syntax for the gethostbyname function:

($name, $aliases, $addrtype, $len, $addr)
    = gethostbyname ($nameString);

Here, $nameString is the machine name to look for. The returned values from the gethostbyname function are the same as those for gethostbyaddr. Look at Listing 15.6. The host name entered by the user may have leading or trailing blanks. These are removed by the statement on line 5.


Listing 15.6. Using gethostbyname.
1 #!/usr/bin/perl
2
3  print ("Enter a machine name or Internet site name:\n");
4  $machine = <STDIN>;
5  $machine =~ s/^\s+|\s+$//g;
6  if (!(($name, $altnames, $addrtype, $len, @addrlist) =
7          gethostbyname ($machine))) {
8          die ("Machine name $machine not found.\n");
9  }
10 print ("Equivalent addresses:\n");
11 for ($i = 0; $i < @addrlist; $i++) {
12         @addrbytes = unpack("C4", $addrlist[$i]);
13         $realaddr = join (".", @addrbytes);
14         print ("\t$realaddr\n");
15 }

The gethostent, sethostent, and endhostent functions enable you to get one entry at a time from the /etc/hosts file. The sethostent() function call rewinds the /etc/host file access to ensure that a call to the gethostent() function returns the record. The endhostnet() call closes further accesses to the /etc/hosts file.

Here's the syntax for the gethostent function:

($name, $aliases, $atype, $alen, $addrs) = gethostent();

The first call to gethostent returns the first element and each subsequent call returns the next element. The five-item list returned by the gethostent() call has the same content as the list returned by gethostbyaddr() or gethostbyname(). The list contains the following:

Caution
Be careful when cycling through /etc/hosts because the file may be "faked out" with the use of the name server. Every query into the /etc/hosts entry may wind up being a request for a host name to a site on the Internet. Calling gethostent repeatedly might access and overload valuable resources unnecessarily. Use this function with a bit of restraint.

Here's the syntax for the sethostent function:

sethostent (keepopen);

If keepopen is non-zero, the /etc/hosts file is left open for reading and any previously cached information about the file is kept in memory. If keepopen is set to zero, any cached information in memory is flushed and the file is read again with the first entry available for a call to gethostent(). The endhostent() function accepts no parameters and simply closes the /etc/hosts file after flushing any buffers.

Working with Process Groups Using the getpgrp() Function

A process group in the UNIX environment is a set of processes having the same process group ID. Several process groups can exist at one time. Each process group is identified by a unique integer, known as the process group ID. The getpgrp function retrieves the process group ID for a particular process.

Here's the syntax for the getpgrp function:

$pgroup = getpgrp ($pid);

$pid is the process ID whose group you want to retrieve, and $pgroup is the returned process group ID, which is a scalar value. A call with no parameters (or $pid set to 0) returns the process group ID of the current process.

You can change the process group of a process, provided you have the correct permissions, by using the setpgrp function. Here's the syntax for the setpgrp function:

setpgrp ($pid, $groupid);

$pid is the ID of the process whose group you will change, and $groupid is the process group ID you want to assign this process to.

The getppid Function

Each process in the UNIX environment has its own unique process ID and parent. (There are some exceptions to this rule, such as the init process, but that discussion is beyond the scope of this book.) The process ID is always available as the special variable, $$. To retrieve the process ID for the parent process for itself, the script can call the getppid() function.

Here's the syntax for the getppid function:

$parentid = getppid();

$parentid is the parent process ID of your program.

The most common use of the getppid function is to pass the process IDs of the parent to a child after a fork. You can use getppid with fork to ensure that each of the two processes produced by the fork knows the process ID of the other. Listing 15.7 illustrates a sample call.


Listing 15.7. Getting the parent process ID.
1 #!/usr/bin/perl
2
3 $otherid = fork();
4 if ($otherid == 0) {
5         $parentID = getppid();
6     printf "I am junior $$ child of $$\n";
7 } else {
8         printf "I am the parent with an ID of $$ \n";
9 }

The output from a sample run would be

I am junior 5423 child of 5422
I am the parent with an ID of 5422

Getting and Changing the Priority of a Process

Processes in the UNIX environments run at a priority level. Processes with the highest priority are run by the UNIX scheduler before processes with a lower priority. A Perl script can set its priority within limits by calling the setpriority function. A process can get information on its priority values by calling the getpriority function. Priority level numbers are system dependent.

The setpriority Function

Here's the syntax for the setpriority function:

setpriority ($category, $id, $priority);

The $category variable indicates what processes are to have their priorities altered. The values that $category can take are listed in the resources.ph file. You can use one of the three following values based on the action you want the setpriority function to take:

PRIO_PROCESS This call affects only one process whose process ID is specified in $id. A value of 0 for $id indicates the current process.
PRIO_PGRP This call affects the entire group whose group ID is specified in $id. A value of 0 for $id indicates the group of the current process.
PRIO_USER This call affects all the processes belonging to the user whose uid is specified in $id. A value of 0 for $id indicates the current user with his or her real (not effective) user ID.

The $priority variable is the new priority for the process, group, or user that you selected. The numbers can range from 0 to 31, or 99 for a UNIX machine, though this value is a very system-dependent issue. For example, the priority levels range from -19 to 20 on a Solaris machine where -8 runs at a higher priority than a process running at 9.

The getpriority Function

The function getpriority() gets the current priority for a process, process group, or user. You can set the priority relative to the value returned by getpriority(). A lower priority causes the affected processes to be set to run while allowing other higher priority tasks to run. A higher priority allows the process to hog more system resources.

Caution
The setpriority() function is a bit dangerous to use and is therefore only available to the root user on most UNIX systems.

Here's the syntax for the getpriority function:

$priority = getpriority ($category, $id);

$category and $id are specified in the same manner as they are for the setpriority() call. For example, look at the following fragment of code, which is used to raise the priority of a Perl program:

require "resource.ph";
$currentpriority = getpriority(&PRIO_PROCESS,$$);
setpriority (&PRIO_PROCESS, $userid, $currentpriority + 1);

Working with Protocol Files Using the getprotoent() Function

The getprotoent function is used to get entries in the /etc/protocols file for the protocols database. Here's the syntax for the getprotoent function:

($protoName, $aliases, $number) = getprotoent();

$protoName is the name of a particular system protocol. $aliases is a scalar list of alternative names for this system protocol, with each name being separated from the other by a space. The $number is the ID for the particular system protocol.

The first call to getprotoent returns the first element in /etc/protocols. Each subsequent call then returns the next entry in the /etc/protocols file. getprotoent returns the empty list when the last entry is read.

The getprotobyname() and getprotobynumber() functions are used to search for entries the /etc/protocols file. The getprotobyname function searches for a particular protocol entry by using a name, whereas getprotobynumber() uses the protocol ID. Here is the syntax for the two functions:

($protoName, $aliases, $number) = getprotobyname ($name);
($protoName, $aliases, $number) = getprotobynumber ($number);

Both functions return an empty list if no matching protocol database entries are found.

The setprotoent() and endprotoent() functions are used to access the entries in the /etc/protocols file. The setprotoent function rewinds the /etc/protocols file.

Here's the syntax for the setprotoent function:

setprotoent (keepopen);

If keepopen is non-zero, the /etc/protocols file is left open for reading and any previously cached information about the file is kept in memory. If keepopen is set to zero, any cached information in memory is flushed, and the file is read again with the first entry available for a call to getprotoent(). The endprotoent() function accepts no parameters and simply closes the /etc/protocols file after flushing any buffers.

The getservent Function

The getservent() function is used to search the /etc/services file for entries in the system services database. Here's the syntax for the getservent() function:

($name, $aliases, $portnum, $protoname) = getservent();

$name is the identifier of a particular system service. $aliases is a scalar list of alias names for the system service specified in $name. The names in $aliases are separated from each other by a white space. $portnum is the port number assigned to the system protocol and indicates the location of the port at which the service is residing. The value in $portnum is a packed array of integers, which must be unpacked using unpack with a C* format specifier. $protoname is a protocol name. The first call to getservent returns the first element in /etc/services. Further calls return subsequent entries; when /etc/services is exhausted, getservent returns the empty list.

The setservent() and endservent() functions are used to access entries in the /etc/services file. The setservent() function rewinds the /etc/services file. Here's the syntax for the function:

setservent ($keepopen);

If keepopen is non-zero, the /etc/services file is left open for reading and any previously cached information about the file is kept in memory. If keepopen is set to zero, any cached information in memory is flushed and the file is read again with the first entry available for a call to getservent(). The endservent() function accepts no parameters and simply closes the /etc/services file after flushing any buffers.

The getservbyname and getservbyport functions are used to search the /etc/services file. The getservbyname() function looks for an entry given a name, whereas the getservbyport() function looks for an entry given a port number. Here's the syntax for the getservbyname function:

($name, $aliases, $portnum, $protoName)
   = getservbyname ($searchname, $searchproto);

Here's the syntax for the getservbyport function:

($name, $aliases, $portnum, $protoName)
   = getservbyport ($searchportnum, $searchproto);

$searchportnum and $searchname are the port number and name of the protocol, respectively. $searchproto is the port number and protocol type to search for.

Both functions return the same type of values as the four-element list returned by getservent(). (The empty list is returned if the name and type are not matched.) Similarly, the getservbyport function searches for a service name that matches a particular service port number.

System-Level Functions

This section lists those system-level functions that you're not likely to use but should know about to perform that one special function.

The chroot Function

The chroot function enables you to change the root directory for your program. The root directory is passed on to any child processes created by the application calling the chroot function.

Here's the syntax for the chroot function:

chroot ($dirname);

The $dirname is the pathname of the directory to use as the root directory. The value of $dirname name specified by dirname is appended to every pathname specified by your program and its subprocesses. For example, use a statement like this one to force all further access to files in the /pub directory:

chroot ("/pub");

This forces even absolute pathnames in a program to be prepended with /pub. The chroot function is helpful when writing applications for the World Wide Web because you can limit user access to a known directory tree. Thus, if a user types /etc/passwd, the request is turned into /pub/etc/passwd.

The dump Function

The dump function enables you to generate a UNIX core dump from within your Perl program. It is meant to be used with the undump command. Here's the syntax for this command:

dump[(label)];

label is optional and specifies the place to start for the UNIX undump command.

Caution
Be careful when working with dump and undump. Only the execution state of a program is kept, not the state of the environment. For example, if the code being undumped was accessing a file when the core was dumped, the file will not be open and is therefore not available to the undumped code.

Using the ioctl Function

In UNIX, the ioctl function has been the traditional catch-all for all input/output operations that can fit in the open, read, write, and close functions. The ioctl function is sometimes not portable across some UNIX systems and almost certainly not for Windows NT or Windows 95 systems. However, ioctl is too useful to discard because it may be the only route to get you the extra functionality you need to access terminal and system facilities.

A very good text to read to learn more about ioctl is W. Richard Stevens's Advanced Programming in the UNIX Environment, Addison Wesley, 1992. This book tells you more about ioctl than you'll want to know at one reading. It's a great source of UNIX information.

Here's an example of how to emulate the getkey() function prevalent in DOS machines. The getkey()function returns one character read back from the keyboard. When reading from the keyboard using <STDIN>, the program has to wait until the user hits the Return key. Waiting for the Return key lets the user back up and correct mistakes. This editing feature is available because the terminal is in a "cooked" mode. That is, the terminal driver is smart enough to recognize a Backspace key and take a character off the input queue.

To process each character at one time, you have to place the terminal in "raw" mode. The raw mode passes all typed keystrokes into the reading application without processing. Note that the following lines may be different on your operating system. The following lines are meant to serve only as an example and are adapted from an example in the Perl FAQ by Don Carson (dbc@tc.fluke.com):

$saveioctl = ioctl(STDIN,0,0);     # Gets device info
$saveioctl &= 0xff;               # reset right most bits
ioctl(STDIN,1,$saveioctl | 32);    # Set raw mode

Here's how to read a single character:

sysread(STDIN,$c,1);               # Read a single character

And here's how to put the terminal back in "cooked" mode:

ioctl(STDIN,1,$saveioctl);         # Restore back to original mode

Note that special keys return two-byte codes in the pc world. Check the ordinal value of $c to see if it's 0. If the value is zero, you have to read the next byte to get the special key code. On Linux machines, you have to check to see if the value is 1 instead of 0:

if(ord($c) == 0) {
    sysread(STDIN,$c,1);
    }

The returned value from the second call is the returned code. The values of the returned key codes depend on your operating system. For most UNIX machines, the codes are listed in the keyboard.ph file, which is about 400 lines long. The file will be in the /usr/include/sys directory.

Using the select Call

The select call in Perl can be used in more than one way. If used with one or no parameters, the select call in Perl refers to the default file handle being selected. However, in UNIX systems, the select(2) function has another very useful purpose of selecting which input to receive data from. To get more information about how to use select, refer to the UNIX man page.

By using the select call, an application can literally wait on more than one source of incoming data. For example, you can wait on both input from the keyboard (STDIN) and from other handles at one point in your code. As input arrives, your program can selectively process each input. Handling more input for an application at more than one location using signals, semaphores, or even other child processes is a lot more complicated than using select().

Using select has the side effect that you cannot use buffered input with the <HANDLE> commands. When using select for input, you have to use the sysread() command to get input into your application. The syntax for the sysread command is

sysread(HANDLE, $variable, $len)

where incoming values are placed in $variable. See the man page read(2) for more details.

Also, do not confuse using the select call for getting input from multiple file handles with using the select call for choosing a file handle sending output. When you make the select HANDLE call, you are redirecting all print and printf output to go to the file associated with HANDLE. The select call in this section lets you collect input from more than one source.

The syntax for the select call is defined as this:

($handle,$timeLeft) =select $rbits,$wbits,$ebits,$timeout;

The values of $rbits, $wbits, and $ebits are bitmapped fields, each for read, write, and "execute" attributes of a file handle. The location of each bit corresponds to the file handle number. The way to work with the bits in these fields is to use the vec() and fileno() calls. The $handle returned is the one that caused the select call to return. The $timeLeft variable is non-zero if the timeout is not reached. To wait indefinitely for some action at a handle, use the undef value for $timeout.

The $rbit, $wbit, and $ebit values have to be set for the file handles they represent in select. You can hard code the values, but it's much safer to use the vec() function. The vec() function sets bits in a vector. Here's the syntax for the vec() function:

vec($id, $index, $value);

The $index bit in the $Id is set to $value. The $value is either 0 or 1. You have to set the bits for reading, writing, and executing in three separate vectors. The index of each bit is determined by the file handle. To get the file handle as an integer, make a call to fileno(HANDLE). In the current implementation you are limited to 32 file handles. On AIX machines, you can use Ipc message queue identifiers as well as file handles.

For example, to set the bits for reading and writing to a socket as well as to standard input, as well as a socket, MYSOCKET, you would make the calls shown in Listing 15.8.


Listing 15.8. Using the select call.
 1 #!/usr/bin/perl
 2 $wbits =0 ; $rbits = 0;
 3 $ebits = 0;
 4 vec($rbits,fileno(MYSOCKET),1) = 1; # read from socket.
 5 vec($wbits,fileno(MYSOCKET),1) = 1;  # enable write vector
 6 vec($rbits,fileno(STDIN),1) = 1;  # enable read vector
 7 $ein = $rin | $win;  # for both reading & writing on all handles
 8
 9 while(1) {
10 ($theFile,$timeleft) = select($rbits, $wbits, $ebits, $timeout);
11 if ($timeleft == 0 ) {
12     &doIdleTasksHere();
13     }
14 &process($theFile);
15 }

The $timeout value can be used to set up a timer by using a call with the bit masks set to undef. The select call actually provides a better timer resolution using milliseconds than the sleep function, which works in one-second granularity. The time "slept" is never exactly what you ask it to be, but it's close enough to what you ask on most systems. To get a timer for 300 milliseconds, use this call:

select(undef, undef, undef, 0.3);

The syscall Function

The syscall() function is a system-dependent function that enables you to call the operating system directly. Most scripts that use the syscall function are nonportable even across platforms with different types of UNIX operating systems.

You need the file syscall.ph in order to use the syscall function. Here's the syntax for the syscall function:

syscall arguments, ... ;

The first item in the arguments list is a call to a subroutine that returns a token ID of the system function being called. The subroutines are defined in the syscall.ph file. Listing 15.9 shows a sample syscall.ph file.


Listing 15.9. The syscall.ph file.
  1 if (!defined & SYS_SYSCALL_H) {
  2 eval 'sub _SYS_SYSCALL_H {1;}';
  3     eval 'sub SYS_setup {0;}';
  4     eval 'sub SYS_exit {1;}';
  5     eval 'sub SYS_fork {2;}';
  6     eval 'sub SYS_read {3;}';
  7     eval 'sub SYS_write {4;}';
  8     eval 'sub SYS_open {5;}';
  9     eval 'sub SYS_close {6;}';
 10     eval 'sub SYS_waitpid {7;}';
 11     eval 'sub SYS_creat {8;}';
 12     eval 'sub SYS_link {9;}';
 13     eval 'sub SYS_unlink {10;}';
 14     eval 'sub SYS_execve {11;}';
 15     eval 'sub SYS_chdir {12;}';
 16     eval 'sub SYS_time {13;}';
 17     eval 'sub SYS_prev_mknod {14;}';
 18     eval 'sub SYS_chmod {15;}';
 19     eval 'sub SYS_chown {16;}';
 20     eval 'sub SYS_break {17;}';
 21     eval 'sub SYS_oldstat {18;}';
 22     eval 'sub SYS_lseek {19;}';
 23     eval 'sub SYS_getpid {20;}';
 24     eval 'sub SYS_mount {21;}';
 25     eval 'sub SYS_umount {22;}';
 26     eval 'sub SYS_setuid {23;}';
 27     eval 'sub SYS_getuid {24;}';
 28     eval 'sub SYS_stime {25;}';
 29     eval 'sub SYS_ptrace {26;}';
 30     eval 'sub SYS_alarm {27;}';
 31     eval 'sub SYS_oldfstat {28;}';
 32     eval 'sub SYS_pause {29;}';
 33     eval 'sub SYS_utime {30;}';
 34     eval 'sub SYS_stty {31;}';
 35     eval 'sub SYS_gtty {32;}';
 36     eval 'sub SYS_access {33;}';
 37     eval 'sub SYS_nice {34;}';
 38     eval 'sub SYS_ftime {35;}';
 39     eval 'sub SYS_sync {36;}';
 40     eval 'sub SYS_kill {37;}';
 41     eval 'sub SYS_rename {38;}';
 42     eval 'sub SYS_mkdir {39;}';
 43     eval 'sub SYS_rmdir {40;}';
 44     eval 'sub SYS_dup {41;}';
 45     eval 'sub SYS_pipe {42;}';
 46     eval 'sub SYS_times {43;}';
 47     eval 'sub SYS_prof {44;}';
 48     eval 'sub SYS_brk {45;}';
 49     eval 'sub SYS_setgid {46;}';
 50     eval 'sub SYS_getgid {47;}';
 51     eval 'sub SYS_signal {48;}';
 52     eval 'sub SYS_geteuid {49;}';
 53     eval 'sub SYS_getegid {50;}';
 54     eval 'sub SYS_acct {51;}';
 55     eval 'sub SYS_phys {52;}';
 56     eval 'sub SYS_lock {53;}';
 57     eval 'sub SYS_ioctl {54;}';
 58     eval 'sub SYS_fcntl {55;}';
 59     eval 'sub SYS_mpx {56;}';
 60     eval 'sub SYS_setpgid {57;}';
 61     eval 'sub SYS_ulimit {58;}';
 62     eval 'sub SYS_oldolduname {59;}';
 63     eval 'sub SYS_umask {60;}';
 64     eval 'sub SYS_chroot {61;}';
 65     eval 'sub SYS_prev_ustat {62;}';
 66     eval 'sub SYS_dup2 {63;}';
 67     eval 'sub SYS_getppid {64;}';
 68     eval 'sub SYS_getpgrp {65;}';
 69     eval 'sub SYS_setsid {66;}';
 70     eval 'sub SYS_sigaction {67;}';
 71     eval 'sub SYS_siggetmask {68;}';
 72     eval 'sub SYS_sigsetmask {69;}';
 73     eval 'sub SYS_setreuid {70;}';
 74     eval 'sub SYS_setregid {71;}';
 75     eval 'sub SYS_sigsuspend {72;}';
 76     eval 'sub SYS_sigpending {73;}';
 77     eval 'sub SYS_sethostname {74;}';
 78     eval 'sub SYS_setrlimit {75;}';
 79     eval 'sub SYS_getrlimit {76;}';
 80     eval 'sub SYS_getrusage {77;}';
 81     eval 'sub SYS_gettimeofday {78;}';
 82     eval 'sub SYS_settimeofday {79;}';
 83     eval 'sub SYS_getgroups {80;}';
 84     eval 'sub SYS_setgroups {81;}';
 85     eval 'sub SYS_select {82;}';
 86     eval 'sub SYS_symlink {83;}';
 87     eval 'sub SYS_oldlstat {84;}';
 88     eval 'sub SYS_readlink {85;}';
 89     eval 'sub SYS_uselib {86;}';
 90     eval 'sub SYS_swapon {87;}';
 91     eval 'sub SYS_reboot {88;}';
 92     eval 'sub SYS_readdir {89;}';
 93     eval 'sub SYS_mmap {90;}';
 94     eval 'sub SYS_munmap {91;}';
 95     eval 'sub SYS_truncate {92;}';
 96     eval 'sub SYS_ftruncate {93;}';
 97     eval 'sub SYS_fchmod {94;}';
 98     eval 'sub SYS_fchown {95;}';
 99     eval 'sub SYS_getpriority {96;}';
100     eval 'sub SYS_setpriority {97;}';
101     eval 'sub SYS_profil {98;}';
102     eval 'sub SYS_statfs {99;}';
103     eval 'sub SYS_fstatfs {100;}';
104     eval 'sub SYS_ioperm {101;}';
105     eval 'sub SYS_socketcall {102;}';
106     eval 'sub SYS_klog {103;}';
107     eval 'sub SYS_setitimer {104;}';
108     eval 'sub SYS_getitimer {105;}';
109     eval 'sub SYS_prev_stat {106;}';
110     eval 'sub SYS_prev_lstat {107;}';
111     eval 'sub SYS_prev_fstat {108;}';
112     eval 'sub SYS_olduname {109;}';
113     eval 'sub SYS_iopl {110;}';
114     eval 'sub SYS_vhangup {111;}';
115     eval 'sub SYS_idle {112;}';
116     eval 'sub SYS_vm86 {113;}';
117     eval 'sub SYS_wait4 {114;}';
118     eval 'sub SYS_swapoff {115;}';
119     eval 'sub SYS_sysinfo {116;}';
120     eval 'sub SYS_ipc {117;}';
121     eval 'sub SYS_fsync {118;}';
122     eval 'sub SYS_sigreturn {119;}';
123     eval 'sub SYS_clone {120;}';
124     eval 'sub SYS_setdomainname {121;}';
125     eval 'sub SYS_uname {122;}';
126     eval 'sub SYS_modify_ldt {123;}';
127     eval 'sub SYS_adjtimex {124;}';
128     eval 'sub SYS_mprotect {125;}';
129     eval 'sub SYS_sigprocmask {126;}';
130     eval 'sub SYS_create_module {127;}';
131     eval 'sub SYS_init_module {128;}';
132     eval 'sub SYS_delete_module {129;}';
133     eval 'sub SYS_get_kernel_syms {130;}';
134     eval 'sub SYS_quotactl {131;}';
135     eval 'sub SYS_getpgid {132;}';
136     eval 'sub SYS_fchdir {133;}';
137     eval 'sub SYS_bdflush {134;}';
138     eval 'sub SYS_sysfs {135;}';
139     eval 'sub SYS_personality {136;}';
140     eval 'sub SYS_afs_syscall {137;}';
141     eval 'sub SYS_setfsuid {138;}';
142     eval 'sub SYS_setfsgid {139;}';
143     eval 'sub SYS__llseek {140;}';
144 }
145 1;

The remainder of the arguments must be precisely what the system call requires, or the calling application will fail. A non-root application will also fail if it makes a non-system call that requires root privilege. Passed arguments are translated into system call equivalents as follows: Numbers are sent as integers. Floating-point numbers are scaled to the nearest integer. Strings are passed by reference. Any strings expecting a response back from the call have to be long enough to store the returned results. A call for writing directly to a file handle is shown in Listing 15.10.


Listing 15.10. Using the syscall function.
#!/usr/bin/perl
require 'syscall.ph';

$hnd  =  syscall(&SYS_open,"myfile",$flgs);
syscall(&SYS_write, $hnd, "Hello!!!\n", 9);
syscall(&SYS_close,hndl);

You can force literals to be interpreted as numbers if you add a zero to the variable. For example, a filename of x would be interpreted as the string "x", not the letter x. However, "x"+0 will cause a number to be passed to the called function. See Listing 15.9 for a list of available system calls on a typical UNIX system.

Summary

This chapter has introduced you to Perl functions for accessing system facilities. Using system('function') or using back quotes is a costly way of executing commands in the system. Various utilities exist in Perl for manipulating the hosts, networks, systems services, and protocols on a UNIX system.