Feb 2007

Mixed Languages in One Project

Many projects require different languages as either separate or integral parts of the overall project impetus. Indeed some coding projects have code that generates code for other parts of the projects. Most common among such tools is using lexical builders to create code to be compiled. In this text an example project that uses several different languages for it's components and the problems that can crop up with using several different languages in one project.

The Project - User Reporting Tools

The pwutils package at this site provides a variety of very simple yet useful for both everyday and educational purposes. The tools basically retrieve a variety of different user data from different sources about accounts and groups. Many administrators and programmers end up having to do so anyhow. Instead of going through each script and/or program a one of each:

The source examples have enough commenting to explain what they do to avoid the section by section tour usually done at this site. They are shown to illustrate the large difference in code syntax and grammar. A very small beginning project was chosen to keep the scope within sane borders of the text's context, however, those of larger magnitude not related to this project will be mentioned in the series.

The pwutils project has very few files at present. The programs and scripts are (with their accompanying language):

pwdf           Perl
pwgrprep       Perl
pwgrps         Shell
pwuser         C
pwuserep       Python
pwutils        Shell

pwuser.c

The pwuser program is a very shallow copy of the BSD userinfo command.

#include <grp.h>
#include <pwd.h>

#include <sys/types.h>

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

#define PN "pwuser" /* The name of the program */

void getuserinfo (char *username);
void usage (void);

/*
 * getuserinfo: Query the pwdb API for user information
 *
 * Required   : Username
 * Returned   : void
 */
void getuserinfo (char *username)
{       
        struct passwd *user_pwd_info; /* Allocate passwd entry structure */
        struct group  *user_grp_info; /* Allocate group entry structure  */
        char          **user_members; /* Group memberships list          */

        user_pwd_info = getpwnam(username); /* Populate passwd structure */

        if (!username)
                printf("User %s not found", username); /* if not found */

                /* Populate the group structure */
        user_grp_info = getgrgid(user_pwd_info->pw_gid);

                /* Now just print out the data we are interested in */
        printf("UserID: %d\n", user_pwd_info->pw_uid);
        printf("GroupID: %d\n", user_pwd_info->pw_gid);
        printf("Username: %s\n", user_pwd_info->pw_name);
        printf("Default Group: %s\n", user_grp_info->gr_name);
        printf("Home Directory: %s\n", user_pwd_info->pw_dir);
        printf("Default Login Shell: %s\n", user_pwd_info->pw_shell);
}

void usage (void)
{       
        printf("Usage: %s [arg1 arg2 arg3...]\n", PN);
        printf("Usage: %s [user1 user2 user3...]\n", PN);
        printf("Usage: usage\n");
}

/*
 * Main: Required - username(s)
 */
int main (int argc, char *argv[])
{       
        int c;
  
                /* Ooops - nothing entered */      
        if (argc <= 1) {
                printf("Syntax error\n");
                usage();
                return 1;
        }

                /* And again ... */
        if (strcmp(argv[1], "usage") == 0) {                  
                usage();
                return 0;
        }

                /* Process all usernames in a simple loop */
        for (c = 1; c < argc; c++) {
                getuserinfo(argv[c]);

                if (argv[c + 1])
                        printf("\n");

        }

        return 0; /* we were good ... */
}

All in all not an overly complex program. The methodology was almost blind copied right out of a programming book with minor changes.

pwgrprep.pl

pwgrprep is a Perl program that does very pretty printing using Perl's format directive:

#!/usr/bin/env perl

# Signals of interest and what to do with them
$SIG{'INT' } = 'interrupt';     $SIG{'QUIT'} = 'interrupt';
$SIG{'HUP' } = 'interrupt';     $SIG{'TRAP'} = 'interrupt';
$SIG{'ABRT'} = 'interrupt';     $SIG{'STOP'} = 'interrupt';

my @group_file = load_file("/etc/group"); # File pointer to the group file
my @groupnames; # Empty array of groups

# For every group parse out : and build a list of who is in each
for (my $i = 0; $i <@group_file; $i++) {
    my @temp = split(":", $group_file[$i]);
    push @groupnames, $temp[0];
}

# Our format statement - horrid looking because xhtml is tough to show this in
format =
@<<<<<<<<< @<<<<< \
        @<<<<<<<<<<<<<<<<
$name,                $gid,  @who
                             @<<<<<<<<<<<<~~
.

$name = "Group Name";
$gid  = "Group ID";
@who  = "Members";
write();
# The above creates the header

# Now populate the memberships and print them out on the fly
for (my $j = 0; $j <@groupnames; $j++) {
    my ($grpname, $grpw, $ggid, @members);
    ($grpname,
     $grpw,
     $ggid,
     @members) = getgrnam($groupnames[$j]);

    $name = $grpname;
    $gid  = $ggid;
    @who  = @members;
    write();

}

# A *very* simple interrupt handler
sub interrupt {
    my($sig) = @_;
    die $sig;
    die;
}

# Generic file loader
sub load_file {
    my ($file) = shift; # what were reading
    my @flist;      # where were gonna stick it

    open(FILE, $file);
    @flist = &ltFILE>;
    close FILE;

    return(@flist); # send the list back to the caller
}                                                             

Not a very difficult Perl program at all, open a file and format some data.

pwuserep.py

pwuserep is similar to grouprep - for users.

#!/usr/bin/env python

import grp # Import the group db API
import pwd # Import the passwd db API

def fixup_userinfo(pwent): # Fixup the fields from pwdb API calls
        try:
                grinfo = grp.getgrgid(pwent[3])
        except:
                grinfo = "?"

        return pwent[0], pwent[2], pwent[3], grinfo[0] ,pwent[5], pwent[6],

def printfields(row):      # Format the coloums and rows
        k = 0
        for j in row:
                if k == 0:
                        print str(j).ljust(11),
                elif k == 1:
                        print str(j).ljust(5),
                elif k == 2:
                        print str(j).ljust(5),
                elif k == 3:
                        print str(j).ljust(12),
                elif k == 4:
                        print str(j).ljust(20),
                else:
                        print str(j).ljust(16)
                k = k + 1

# Main

# Initially print out a header
printfields(('Name','UID','GID','Pri Group','Home Dir','Shell'))
allpwent = pwd.getpwall() # Get all pwdb entries

for i in allpwent:        # For each entry, print out a row
        printfields(fixup_userinfo(i))

Again a simple API call and printing formatting. One thing to note, this was definitely a first pass script and the printing methodology is . . . questionable.

pwgrps.sh

pwgrps is a clone of the GNU command of the same name for BSD systems.

#!/bin/sh

progname=${0##*/} # Who we are
toppid=$$         # Our top PID
results=/dev/null # Where to stuff it
trap "exit 1" 1 2 3 15 # Traps we want

usage() # How to use this script
{
        cat <<_usage_
Usage: ${progname} [arg1 arg2 arg3...]
Usage: ${progname} [user1 user2 user3...]
Usage: ${progname} usage
_usage_
}

if [ $# -eq 0 ];then # In case someone forgot a username
        echo "No command or username specified"
        usage
        exit 1
fi

for i in $@ # Process each username, see what groups they are in
do
        if [ "$i" = 'usage' ];then
                usage
                exit 0
        fi

        grouplist=$(id -Gn $i 2>/dev/null) # Essentially use id for the job
        echo "$i is in: ${grouplist}"
done

exit 0

Ultra simple - call another command to print groups a user is in on the fly.

The Unifying Bits

There are several areas where these programs and scripts intersect:

  1. Makefile
  2. pwutil script
  3. $PATH
  4. install or binstall

The biggest problem is determining if the needed pieces are on the given host, this can easily be solved by adding some lines to an install.sh script:

#!/bin/sh

PROG=${0##*/}
TOPPID=$$
trap "exit 1" 1 2 3 15

bomb()
{
    cat >&2 <<ERRORMESSAGE

ERROR: $@
*** ${PROG} aborted ***
ERRORMESSAGE
    kill ${TOPPID}
    exit 1
}

echo Checking for interpreters and compilers...

which perl   ; [ $? -gt 0 ] && bomb Perl interpreter not found
which python ; [ $? -gt 0 ] && bomb Python interpreter not found
which bash   ; [ $? -gt 0 ] && bomb Bash interpreter not found
which gcc    ; [ $? -gt 0 ] && bomb GNU C compiler not found

Summary

Using multiple languages in one project is not uncommon. Often it is implicit, GNU autotools use software to generate software for easier management.