January 2006

Rewriting a Perl util in C Part I

There is a utility called service that can be used to shortcut the path of init (or rc) scripts on several platforms. The service utility is written in Perl. As an exercise service is being rewritten in C. The command needs to be able to do very few operations. The version presented in this text will have room for improvement requiring some additional functions and operational changes.

Basic Requirements

Determine the location of the init scripts
The Perl version forces the administrator to specify the platform using make directives and a helper shell script. The C version will simply look for the directory based on a list of common paths.
Build the entire string
In the Perl version, the full path to the init script is built first, then the path and operation are passed directly to the shell using the system() function. In the C version, the path and operation are built as one string which is passed to the libc system() function.
Perform the command
The last step is simply passing the entire string to the shell.

Includes and Prototype

The following includes and prototype are at the beginning of the source file:

#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>

#include <sys/types.h>
#include <sys/stat.h>

Helper Functions

The first draft of service in C has one helper function:

int check_handle(char *dir)
{
        struct stat statbuf;

        return (stat(dir, &statbuf));
}

First allocate a structure for the status buffer then return the results (not the buffer or handle) of the stat() function.

The check_handle() function does little but it uses a lot, specifically the status buffer. In the context of a function it is assured that the buffer is blasted clean after each use because it is local to the function.

The main() Program

The actual program does little so far, first the initialization portion:

        int x;
        char sh_cmd[PATH_MAX];
        static char *init_pth[] = {"/etc/init.d/","/sbin/init.d/","/etc/rc.d/",};

Only three variables:

  • int x - counter.
  • char sh_cmd - what will hold the entire command string.
  • static char init_pth - a list of paths to try out.

The next step is to see if there is a path that matches one of the paths defined in the init_pth list:

        for (x = 0; x  >= sizeof(init_pth); x++) {
                if (check_handle(init_pth[x]) == 0) {
                        strcpy(sh_cmd, init_pth[x]);
                        break;
                } else {
                        printf("Could not find an init path\n");
                        return 1;
                }
        }
  • Set up a loop to iterate over the init_pth list.
  • Run the helper function check_handle() to see if the path that is being examined exists.
  • If the path exists, copy it into the sh_cmd string and break out of the loop (stop checking).
  • If no path is found print a simple error and return (essentially exit since it is in main()) 1.

Time to build up the string, this is done by simply concatenating onto sh_cmd argument 1, a space, and argument 2 thus creating a syntax of service service_name operation:

        strcat(sh_cmd, argv[1]);
        strcat(sh_cmd, " ");
        strcat(sh_cmd, argv[2]);

The last operation, pass the entire string to the shell and return a success signal (0):

        system(sh_cmd);

        return 0;

With draft one of service in C done, it is time to look at some of the flaws.

Problems with service C Draft

Using strcat()

The first glaring issue is the use of the strcat() function. Concatenation onto a string with two possibly unknown string lengths is generally not a good idea:

[mui@vela:~/src/service-0.5$] ./a.out mmmmmmmmmmmmmmmmmm\
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm\
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm\
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm\
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm\
mmmmmmmmm
Segmentation fault

To Break from main() or Not?

Should the directory check even be in a separate function? Is it possible to perform the operation in a fast manner within the confines of the main() routine? If a routine is needed could recursion make it faster or would it add overhead that is not necessary?

system() Pragma

Should the system() function be checked? Is using system() the safest way to execute the init script? If there is an error how should it be handled?

Missing Features

There are several features from the original Perl version that are missing:

  • A verbose option
  • Listing the contents of the init directory
  • Just showing the path to the init directory

Summary and Next Time

At first glance the C version of service seems simpler in code terms, however, next time a great deal of code and fixes have to be applied. Once the next version is finished, only after that will it be possible to do an equitable comparison.