March 2006

Perl to C Part II

In the first part of this series a simple version of a service utility originally written in Perl was rewritten in C. The first version of the C service utility had a few lingering items. The question to be hopefully answered in this text is after fixing a few of these, was it worth the effort?

Some New Functions

To start, a slew of new functions are added:

size_t strlcat (char *, const char *, size_t);
size_t strlcpy(char *, const char *, size_t);
int dirlist(char *);
void usage(void);

strlcat and strlcpy

This is a simple verbose copy of the OpenBSD strl function of the same name. Note that the license would now have to reflect this change:

size_t strlcat (char *dst, const char *src, size_t dst_sz)
{
    size_t len = strlen(dst);

    if (dst_sz < len)
        return len + strlen(src);

    return len + strlcpy (dst + len, src, dst_sz - len);
}

The next simplest function added is the usage message.

usage

void usage(void)
{
        printf("Usage: service service-name operation\n");
        printf("Usage: service [list|show]\n");
        printf(" list - list init scripts\n");
        printf(" show - show init directory path\n");
}

nothing too fancy there, next yet another copy, right from the GNU programming manual.

dirlist

int dirlist (char * dir)
{
    DIR *dp;
    struct dirent *ep;
    int i;

    i = 0;
    dp = opendir(dir);
    if (dp != NULL) {
            while (ep = readdir (dp)) {
                    if (i > 1)
                        printf("%s\n", ep->d_name);

                    i++;
            }
            (void) closedir (dp);
    } else
            perror ("Could not open directory for reading");

    return 0;
}

This version is somewhat different, but it makes for a good starting point. Note that it would be a lot nicer if it used well formatted printing.

Changes to main

With all of the new stuff added, a lot of the main() program had to be reworked. Some new includes were tossed in but otherwise the data structure used in the first one remains. Now the new top part of main():

int main (int argc, char **argv)
{
    int x, c;
    int show;
    char sh_cmd[PATH_MAX];

    c = 1;
    show = 0;

We set up a counter and show is whether or not the show command is going to be passed:

    if (strcmp(argv[1], "show") == 0)
            show++;

In the main while loop, if show is specified, it simply prints out the full path to the service.

 x = 0;
    while (x <= (sizeof(init_pth)/3)) {
            if (check_handle(init_pth[x]) == 0) {
                    strcpy(sh_cmd, init_pth[x]);
                    if (show) {
                            printf("%s\n", init_pth[x]);
                            return 0;
                    }

                    break;
            } else
                    x++;
    }

Finally, check for the list and usage commands, put the full command together, and run it:

 if (strcmp(argv[1], "list") == 0) {
            dirlist(sh_cmd);
            return 0;
    } else if (strcmp(argv[1], "usage") == 0) {
            usage();
            return 0;
    }

    if (argc <= 2) {
            printf("Syntax error\n");
            usage();
            exit(1);
    }

    strlcat(sh_cmd, argv[c], sizeof(sh_cmd));
    strcat(sh_cmd, " ");
    strlcat(sh_cmd, argv[++c], sizeof(sh_cmd));

    system(sh_cmd);

    return 0;
}

Not much and it still lacks some of the functionality of the Perl version.

Summary & Thanks

In summary, if there is a very compelling reason (like someone actually doesn't have Perl loaded) the rewrite is exactly what was first stated - a good exercise - otherwise not really worth the time. This opens the door up for discussion about whether or not prototyping in one language for another is a good idea.

Special thanks go to Geffrey Velasquez and Matt B for some corrections in the main while loop.