suckless

libsl is an extremely small library used to write better and clearer C programs. The code was created by the suckless community, a small group of C hackers and developers creating powerful yet simple programs for Linux designed to be lightweight, configurable, and easier to maintain then the many other unnecessarily bloated alternatives. You can read more about their philosophy on their website. Some of their most notable projects include dwm, dmenu, st, and surf.

libsl is composed of tiny C99 source and headers files designed to be simply copied into a project’s source tree. This allows the library to be easily used and easily modified to fit every project’s need.

All of libsl is protected under the MIT license, allowing you to use it in your own projects with little strings attached. See LICENSE in the source tree for more.

arg.h

One of the most utilized and most important files in libsl is arg.h, a C header designed to easily interpret short options from the command line. This removes the necessity to copy complex boiler plate or clunky libraries to simply see if an option has been supplied by the user. While arg.h does not support long options (--option vs -o), if your project’s CLI is relatively straight forward it is a great, simple, and lightweight method to quickly parse short options in your C code.

To start reading short options simply use the macro ARGBEGIN followed by a { in your C program’s main function, don’t forget to #include "arg.h" at the top of the file. This macro loops through all the command line arguments and finds the short option (beginning with a single -) and allows you to select one in a switch statement by using case 'a':, where a can be any character for the desired option.

arg.h also defines other macros to read data in the ARGBEGIN block. One example is ARGC() which is defined to ARGBEGIN’s internal variable argc_, the character for the short option currently being interpreted.

ARGF() is another useful macro to get an argument after an option. For example if a program prog is run as prog -s string then ARGF() in the s case will return string as a C string. This can also be converted to a number through a function such as atoi. A similar macro EARGF(x) can also run the supplied function upon an error. It is common to define a usage() function in suckless projects such as the one below.

void
usage(void)
{
	die("usage: %s [-ab] [-c STR] [-d STR] [-n NUM | -NUM]", argv0);
}

die() is a function in libsl defined in util.c, see the section below for more.

arg.h also defines a string, argv0, which is set to the program’s name, equivalent to argv[0], but global allowing it to be used throughout your code, like in the usage message above.

In this example EARGF(usage()) would display usage info on an error reading the next string.

While ARGF() with atoi() can be used to get a number from given arguments, it is often faster to use the -NUM format (eg prog -4 to get the number 4 instead of prog -n 4). In order to achieve this I have added my own macros to arg.h that can be used.

/* handles -NUM syntax */
#define ARGNUM case '0':\
               case '1':\
               case '2':\
               case '3':\
               case '4':\
               case '5':\
               case '6':\
               case '7':\
               case '8':\
               case '9'

/* get option number */
#define ARGNUMF()	(brk_ = 1, atoi(argv[0]))

The first is ARGNUM which is used in replace of a case statement in the ARG block (eg case 'n': becomes ARGNUM:). Inside of this case replacement ARGNUMF() is used to get the number given by the argument supplied by the user. These macros, while only needing to be defined for 1-9, work on any number over 10 as well.

After running through all the options you want—remembering to use break after each one, possibly including a default in case an argument given is not supported—ARGBEGIN and the curly bracket we opened need to be closed via } ARGEND. ARGEND is essentially just closing all the brackets ARGBEGIN opened, not counting the one we placed.

A complete ARG block example is shown below, don’t forget that it needs to be placed in inside the int main(int argc, char *argv[]) function.

ARGBEGIN {
case 'a':
	printf("option a selected\n");
case 'b':
	printf("option b also runs if your not careful\n");
	break;
case 'c':
	printf("options can also have arguments: %s\n", ARGF());
	break;
case 'd':
	printf("support for error messages: %s\n", EARGF(usage()));
	break;
case 'n':
	printf("they can also be numbers: %d\n", atoi(EARGF(usage())));
	break;
ARGNUM:
	printf("or in this format: %d\n", ARGNUMF());
	break;
case 'v':
	verbose++; /* define before ARGBEGIN block */
	printf("options can also be repeated: %d\n", verbose);
	break;
default:
	usage();
} ARGEND;

Note that arg.h modifies argv and argc. If, for example, after parsing the arguments you now want to read any strings after, such as file names, argv[0] is the first argument after the valid options. So if prog -s option filename anotherfile is used, the s case in ARGBEGIN is run with the argument option, then argv[0] will be equal to filename, argv[1] to anotherfile, etc.

util.c

The next file in libsl is util.c. This file defines many general purpose C functions used throughout different projects.

One function found here which is common in many C projects, including Linux, is die(). This utility takes a formatted string, adds a newline at the end, and prints it to stderr before exit()ing with a return value of 1. This is useful for writing simple and clean error messages which need to quit the program. die() is used above in the usage() example as well as the quick examples below.

struct MyData data;
data->str = init_str();
if (!data->str || !strcmp(data->str, ""))
	die("%s: error: could not init data->str", argv0);
int strlen = 20;
char *str;
if (!(str = calloc(strlen, sizeof(char))))
	die("fatal: could not calloc() %u bytes", strlen * sizeof(char));

As well as formatting the output similar to printf(), libsl’s die() also supports automatic error messages for functions which set the C global errno. If the string given ends in a colon (:), die() will append a space and the error message thrown by a called function with errno support. Thus the above calloc example can be replaced with the more adaptive:

int strlen = 20;
char *str;
if (!(str = calloc(strlen, sizeof(char))))
	die("calloc:"); /* example error: "calloc: out of memory" */

The above example is the bases for another useful function util defines, ecalloc(). This new function makes sure the pointer allocated by calloc is not NULL, which indicates an error occurred. Using this new wrapper we can replace the example above with the following.

int strlen = 20;
char *str = ecalloc(strlen, sizeof(char));

This function can also be duplicated for other similar functions such as realloc(), malloc(), strdup(), etc. A new example erealloc() function is displayed below.

void *
erealloc(void *p, size_t size)
{
	if (!(p = realloc(p, size)))
		die("realloc:");

	return p;
}

util.h

The header, as well as defining the previous functions, creates some useful macros.

Two macros which are often copy and pasted between projects and files is MAX() and MIN(). They are fairly simple, two integers are given as arguments and the biggest or smallest one is returned respectively.

#define MAX(A, B)   ((A) > (B) ? (A) : (B))
#define MIN(A, B)   ((A) < (B) ? (A) : (B))
MAX(4, 8);     /* => 8   */
MIN(-38, -18); /* => -38 */
MAX(10, -47);  /* => 10  */

In a similar vain BETWEEN() returns whether the first integer supplied is between the next two given integers.

#define BETWEEN(X, A, B)   ((A) <= (X) && (X) <= (B))
BETWEEN(4, -8, 12); /* => 1 (true)  */
BETWEEN(9, 20, 67); /* => 0 (flase) */

LEN() is also often defined here, it is used to return the size of an array.

#define LEN(X)            (sizeof(X) / sizeof((X)[0]))

drw.c

drw.c and its header drw.h are used as an X interfaced for making basic graphical programs, for example dmenu. An in depth overview of the many functions and typedefs offered in this file is coming soon as its own post.

conclusion

I hope this post has be beneficial and will help you create more elegant and cleaner code in C. For other projects putting this philosophy into practice checkout some of my projects, the suckless git repo, and their other recommended projects page.