Skip to main content

POSIX LibC comparison

Directory Recursion

The standard library provides nftw for directory recursion:

#include <nftw.h>
#include <stdio.h>

/* you need to specify a maximum of file descriptors */
#define MAX_FDS 20

static int
display(const char *s, const struct stat *sb, int tflag, struct FTW *ftwbuf)
{
switch (tflag) {
/* no type for cycles */
case FTW_D: /* chose between preorder and postorder at nftw */
return 0;
case FTW_DNR: /* directory not readable */
fprintf(stderr, "<warning> failed to read \"%s\"", s);
return 0;
}
printf("%s\n", s);
return 0;
}

int
main(int argc, char **argv)
{
--argc; ++argv;
if (!argc) return 0;
/* random access */
for (; *argv; ++argv) nftw(*argv, &display, MAX_FDS, 0);
return 0;
}

In comparison tertium provides a simple iterative interface (similar to bsd fts) with optional reproducible access (by sorting the entries and normalizing path arguments) and support to deal with cycles:

#include <tertium/cpu.h>
#include <tertium/std.h>

static int
sort(void *va, void *vb)
{
ctype_dent *a = va;
ctype_dent *b = vb;
return c_str_cmp(a->name, -1, b->name);
}

ctype_status
main(int argc, char **argv)
{
c_std_setprogname(*argv);
--argc; ++argv;
if (!argc) return 0;
ctype_dir dir;
if (c_dir_open(&dir, argv, 0, &sort) < 0) {
c_err_die(1, "failed to read arguments");
}
ctype_dent *ep;
while ((ep = c_dir_read(&dir))) {
switch (ep->info) {
case C_DIR_FSD: /* directory preorder */
case C_DIR_FSDC: /* directory cycle */
case C_DIR_FSDP: /* directory postorder */
break;
case C_FS_FSDNR: /* directory not readable */
c_err_warn("failed to read \"%s\"", ep->path);
break;
}
c_ioq_fmt(ioq1, "%s\n", ep->path);
}
return 0;
}

String

The string routines in the standard library C are quite error prone:

char buf[32];
/* mistake 1: forgot '/0' space and to nil terminate */
strncpy(buf, data, sizeof(buf)); /* need to check for truncation manually */
/* mistake 2: forgot to recalculate the difference */
for (; *argv; ++argv) strncat(buf, *argv, sizeof(buf) - 1);

The tertium provides the modules "arr" and "dyn" for array manipulation, while more verbose, they are quite powerful and much safer for string manipulation (and any kind of sequential data):

ctype_arr arr;
char buf[32];
c_arr_init(&arr, buf)
/* no truncation accepted */
ctype_status r = c_arr_fmt(&arr, "%s", data);
if (r < 0) {
/* it was not possible to concatenate "data" in "arr".
* array state is left unchanged */
return -1;
}
/* truncation accepted */
c_arr_fmt(&arr, "%.*s", c_arr_avail(&arr), data);
/* safe */
ctype_status r = 0;
for (; *argv; ++argv) r |= c_arr_fmt(&arr, "%s", *argv);
if (r < 0) {
/* handle error */
return -1;
}

It always ensures your array is nil terminated.

Safety

Besides providing many functions that will make it easier to program safely, it also has bigger care for safety of common routines:

/* mem routines comparison */
/* libc */
memcpy(buf, buf + 10, sizeof(buf) - 10); /* may pass unnoticed (should be memmove) */
memset(buf, 0, sizeof(buf)); /* insecure for sensitive data */
memcmp(buf, password, sizeof(password)); /* insecure for sensitive data */

/* tertium */
c_mem_cpy(buf, size(buf) - 10, buf + 10) /* works with memory overlap */
c_mem_set(buf, sizeof(buf), 0); /* safe */
c_mem_equal(password, sizeof(password), buf); /* secure */

/* malloc routines comparison */
/* libc */
malloc(x * sizeof(*ptr)); /* may overflow silently */
free(ptr), free(ptr); /* UB (double free); */
realloc((void *)-1, 200); /* UB (non owned region) */

/* tertium */
c_std_alloc(x, sizeof(*ptr)) /* catches overflow */
c_std_free(ptr); c_std_free(ptr); /* fail safely */
c_std_realloc((void *)-1, 200, sizeof(uchar)); /* fails and sets errno */

Interoperability

The routines consider their interoperability where it makes sense, as consequence, they are easy to extend and interact with other routines.

#include <tertium/cpu.h>
#include <tertium/std.h>

/* adding a new format to fmt */
struct user {
char *name;
char *gender;
int age;
};

ctype_status
print_user(ctype_fmt *f)
{
struct user *u = va_arg(p->args, struct user *);
return c_fmt_print(f, "%s (%s) %d", u->name, u->gender, u->age);
}

ctype_status
main(int argc, char **argv)
{
struct user u0 = { "Adam", "Male", 666 };
c_fmt_install('U', &print_user);
/* now any "fmt" function will heritage this verb "U" */
c_ioq_fmt(ioq1, "u0: %U\n", &u0);
c_ioq_flush(ioq1);
return 0;
}

It's possible to add new hashes, new verbs to any routine that deal with formattation, easily replace the internal malloc (although it must, like the original, detect double-free and non-owned regions), use strings (errstr) and numbers (errno) to handle errors, and other useful things.