Write a portmaster in C

Hi.

I read a book about C programming (more that 5 times) and also I read some manual pages from this link. Now I know about functions, pointers, structures, Makefiles, linked lists and ... but I still can't write a simple program. (I want to write a simple portmaster like program). I know I can do this via system(3)() but I don't know how to start writing this. What skills I need to learn and what knowledges I need to know? I searched the forums and read some related topics but I really don't know what should I do to write a simple portmaster with C.
 
It would be useful if you could post a *specific* question. You mentioned you already started mucking about with some code, post your code, explain what goes wrong & what you don't understand. I'm sure someone can explain what *exactly* went wrong.

Specific and concrete questions will yield specific and concrete answers; vague and general questions will typically yield vague and general answer (which are often useless).

That being said, when I'm really stuck at a problem, I like to leave things alone and come back to the problem the next day. It's amazing what a good night's sleep and fresh mind can do!

Also remember that programming is *hard*. You *will* be baffled and at a loss on a regular basis, especially when you're just starting. Almost all programmers went through that phase. Hang in there! It takes practice but it *does* get easier after a while.

Reading a book about programming is *not* learning how to program: it's only the first step. Reading a book several times will give you very little benefits. Imagine learning to ride a bike just by reading a book: pretty much impossible. You need to actually write programs. Preferably a lot. Useful programs, useless programs, unfinished programs, stupid programs, the whole bunch.
 
Thanks for both replies. I'll keep that in my mind for my future questions. Also I've changed the title. Here is my code. Currently it can install a software using a package or a port:

./a.out x11/xterm
./a.out -P x11/xterm

My main questions is:

  • Any suggestion about this code?
  • portmaster has a configuration file. How can I add this feature to this code? I can work with fopen(), fread(), fwrite(), etc, but I don't know how to parse a configuration file with a specific format and write my own syntax.
  • How can I handle dependencies like portmaster?
  • How can I check if it runs as root or a regular user? (in FreeBSD way). I can't find a function to do this in the FreeBSD libraries.

Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>

int
main( int argc, char *argv[])
{
        uid_t curuser;
        curuser = getuid();

        if( curuser != (uid_t)0 )
        {
                printf("You must run this program as root\n");
                exit(EXIT_FAILURE);
        }


        int option;
        int usepackage = 0;
        int length;
        const char *pkgcmd = "pkg_add -rv ";
        const char *portcmd = "cd /usr/ports/";
        char *installcmd;

        while ( (option = getopt(argc, argv, "P")) != -1 )
        {
                switch (option)
                {
                        case 'P': usepackage = 1;
                                        break;
                }
        }
        argc -= optind;
        argv += optind;


        length = strlen( argv[0] );

        char *progname = (char*) malloc( length * sizeof(char) );
        strcpy(progname, argv[0]);

        if( usepackage == 1)
        {
                length += strlen(pkgcmd) + 1;
                installcmd = (char*) malloc( length * sizeof(char) );
                strcpy(installcmd, pkgcmd);
                strcat(installcmd, progname);
        }
        else
        {
                length += strlen(portcmd) +1;
                installcmd = (char*) malloc( length * sizeof(char) );
                strcpy(installcmd, portcmd);
                strcat(installcmd, progname);
                strcat(installcmd, " && make install clean");
        }

        printf("%s\n", installcmd);
        system( installcmd );

        return 0;
}
 
bkouhi said:
portmaster has a configuration file. How can I add this feature to this code? I can work with fopen(), fread(), fwrite(), etc, but I don't know how to parse a configuration file with a specific format and write my own syntax.
Thanks for being interested in C! I'm in a bit of a rush at this moment so I'll only quickly answer the question above, the rest may perhaps come later.

If the config file syntax is simple, you can just read it one line (or even word) at a time and keep track of a state machine of some kind. But if you're thinking of a more fancy syntax, the typical thing to do is to use Lex and Yacc (or their more modern counterparts Flex and Bison). These tools are often used for making compilers, so some general knowledge of compiler building will go a long way. I may have a more elaborate answer for you later today, so perhaps as they say in the movies: to be continued?
 
wblock@ said:
portmaster(8) is a fairly complicated shell script. Rewriting it in C would be a non-trivial job.
What's more, there's probably a reason why it's a shell script and not a C program. If you're looking for an exercise that's fine (and I'll try to answer as many of your questions as I have time for), but you might be trying to use the wrong tool for the job. Don't get me wrong, there's not a whole lot that can't be done in C, but that doesn't mean there might not be an easier way to do it.
 
The portmaster.rc syntax would be the same as the similar support in mergemaster(8)(). See getopts, is an improvement alternative of getopt(1)(), statement in portmaster(8).

% less +/getopts /usr/local/sbin/portmaster

Also read differences between getopt and getopts with examples and comments.

There is a getopts tutorial which explains what all of the syntax and variables mean. In bash, there is also % help getopts, which might be informative.
 
fonz said:
What's more, there's probably a reason why it's a shell script and not a C program. If you're looking for an exercise that's fine (and I'll try to answer as many of your questions as I have time for), but you might be trying to use the wrong tool for the job. Don't get me wrong, there's not a whole lot that can't be done in C, but that doesn't mean there might not be an easier way to do it.

Hello again.

Thank you @fonz and @cpu82. I don't have a special reason to write a portmaster. It would be more useful if I start writing a utility that currently does not exist. I thought a lot about this but I can't find something useful. Any suggestions are highly appreciated.

I found the answer of my last question. getuid(2)() returns the UID of the owner of the process. So I can do for example:

Code:
        uid_t cur_user;
        cur_user = geteuid();

        if( cur_user != (uid_t)0 ) /* "0" is the UID of the root user */
        {
                printf("You must run this program as root\n");
                exit(EXIT_FAILURE);
        }

I updated the code in my #4 post.
 
Last edited by a moderator:
Hi :e

I wrote the following code to parse a configuration file. The content of file is:

Code:
pkg=/usr/sbin/pkg_add
portsdir=/usr/ports/

And this is the code:

Code:
char *directive;
char *pkg_add = NULL;
char *portsdir = NULL;

FILE *conf;
char *conffile = "/usr/local/etc/prog.conf";
char *line = NULL;
ssize_t linelen;

conf = fopen(conffile, "r");

if( !conf )
{       
        printf("Can't open the configuration file. Exiting...\n");
        exit(EXIT_FAILURE);
}

while( feof(conf) == 0 )
{
        getline(&line, &linelen, conf);
        if( (directive = strsep(&line, "=")) != NULL );
        {
                if( strcmp(directive, "pkg") )
                        pkg_add = line;
                else if ( strcmp(directive, "portsdir") )
                        portsdir = line;
        }
}

fclose(conf);


printf("pkg_add: %s\n", pkg_add);
printf("portsdir: %s\n", portsdir);

But it doesn't work as expected:
Code:
[CMD]# ./a.out [/CMD]
pkg_add: (null)
portsdir: portsdir

Next I replaced the if...else statement with two printf():
Code:
while( feof(conf) == 0 )
{
        getline(&line, &linelen, conf);
        if( (directive = strsep(&line, "=")) != NULL );
        {
[B]                printf("directive: %s\n", directive);
                printf("line: %s\n", line);[/B]
        }
}


And got the following output:
Code:
directive: pkg
line: /usr/sbin/pkg_add

directive: portsdir
line: /usr/ports/

directive: 
line: (null)

There are two extra "\n" characters in the output.
 
See getline(3):
Code:
DESCRIPTION
     The getdelim() function reads a line from stream, delimited by the char-
     acter delimiter.  The getline() function is equivalent to getdelim() with
     the newline character as the delimiter.  [color="Red"]The delimiter character is
     included as part of the line, unless the end of the file is reached.[/color]
 
Hi :e

Finally I wrote a code to parse a configuration file. Here is the content of file:

Code:
pkg=/usr/sbin/pkg_add
# this is a comment and below is a blank line. these lines will be ignored.

portsdir=/usr/ports

Code:
        char *filename = "/usr/local/etc/prog.conf";

        FILE *config;

        char *line;
        char *pkg_add; 
        char *portsdir;
        size_t length;

        char *word = NULL;

        config = fopen(filename, "r");

        if( config == NULL )
                exit(EXIT_FAILURE);

        while( line = fgetln(config, &length) )
        {

                line[strcspn(line, "\n")] = '\0';

                if( length == 0 || (line[0] == '#') )
                        continue;

                word = strsep(&line, "=");

                if( strcmp(word, "pkg") == 0)
                        pkg_add = strdup(line);
                if( strcmp(word, "portsdir") == 0)
                        portsdir = strdup(line);

        }

        fclose( config );
 
fonz said:
In short: when shell-scripting, don't use getopt(1), it's deprecated and getopts (see sh(1)) should be used instead. In C, however, using getopt(3) is fine.

Also, if you want the C version of portmaster flags to be the same/compatible with the sh version, you'll probably have to use getopt_long(3)(). With them you can parse for example [cmd=""]--clean-distfiles[/cmd] flag, and create the short version of the same command (with just one 'dash') as well.
 
Thank you @jozze. I was looking for that function.

I've found the answer of my third question. Every installed port has a directory in the /var/db/pkg. In that directory there is a file named +CONTENTS. This file contains lots of useful informations about installed applications including the dependencies, checksums, compile options, etc. But this file has a complex format and in my opinion, parsing that file using primitive C functions is very difficult. I think this is why the portmaster is written in sh instead on C :stud

Here is a example:
Code:
[CMD]% cd /var/db/pkg/conky-1.9.0_1[/CMD]
[CMD]% cat '+CONTENTS'[/CMD]
@comment PKG_FORMAT_REVISION:1.1
[B]@name conky-1.9.0_1[/B]
@comment [B]ORIGIN:sysutils/conky[/B]
@cwd /usr/local
[B]@pkgdep xproto-7.0.24[/B]
@comment [B]DEPORIGIN:x11/xproto[/B]
@pkgdep xextproto-7.2.1
@comment DEPORIGIN:x11/xextproto
@pkgdep libXdmcp-1.1.1
@comment DEPORIGIN:x11/libXdmcp
@pkgdep libXau-1.0.8
@comment DEPORIGIN:x11/libXau
...
...
@comment DEPORIGIN:converters/libiconv
[B]@conflicts conky-awesome-[0-9]*[/B]
bin/conky
@comment [B]MD5:b8836142677082024a6691c16a336cd1[/B]
man/man1/conky.1.gz
@comment MD5:c7355173cf198794c434397cf1370e6c
@comment [B]OPTIONS:-APCUPSD -AUDACIOUS -INOTIFY -LUA -METAR -MOC -MPD -NCURSES -RSS +X11 -XMMS2 -XOAP X11[ +ARGB +DOUBLE_BUFFER -IMLIB2[/B]
@comment OPTIONS:-XFT -LUA_CAIRO -LUA_IMLIB2 ]
share/doc/conky/README
 
Last edited by a moderator:
bkouhi said:
I was looking for that function.
No problem. The C library is very well documented in FreeBSD and on Linux. You could've reached that function by simply checking out the SEE ALSO section of getopt(3)(). Manpages have sometimes more information than the internet sites (just take the ungrateful complex.h for example), and they're really handy when you're writing programs.

By the way, you can consider writing only parts of the portmaster in C (the bottlenecks of the algorithms) and rewrite parsers in python (instead of shell). It can still speed up the process by a small margin. You can use the system(3)() function in C to let program execute those scripts (and not the other way around) which can really speed up the process (I tried it myself, it changed from hours ==> minutes).

However, I think there wouldn't be much gain (appart from experience) from rewriting portmaster in C -- the bottlenecks are the compilations themselves, and not the database wizardry.

The real power of C comes from superb SMP, but both compilers and make programs support it already so ...
 
Back
Top