Saturday, November 01, 2014

Editing ad-hoc config files on Linux

I had a need to edit /etc/network/interfaces file on Ubuntu (from my program). At first I found some awk script on the web, which claimed to do the job, but when I tried, it wasn't addressing all the CRUD cases that I am interested in (also, I didn't want to have system() calls from my C code).
So, I searched for a better utility, and found Augeas - this is fantastic! you have to try it to believe! It has a neat command line utility (augtool) as well as an easy to use C API (+ bindings for many scripting languages, including my fav Lua too :-) ).

The following commands show how easy it is to add/rm an interface (XPATH expressions)

$ sudo augtool # Add an interface at the end (last)
augtool> set /files/etc/network/interfaces/iface[last()+1] eth1
augtool> set /files/etc/network/interfaces/iface[last()]/family inet
augtool> set /files/etc/network/interfaces/iface[last()]/method static
augtool> set /files/etc/network/interfaces/iface[last()]/address 10.1.1.1
augtool> save
Saved 1 file(s)
augtool> 


$ sudo augtool  # Edit the added interface (by name, not position)
augtool> set /files/etc/network/interfaces/iface[. = 'eth1']/netmask 255.255.255.0
augtool> save
Saved 1 file(s)
augtool> set /files/etc/network/interfaces/iface[. = 'eth1']/mtu 1500
augtool> save
Saved 1 file(s)
augtool> 

$ sudo cat /etc/network/interfaces 
auto lo
iface lo inet loopback
iface eth1 inet dhcp
   address 10.1.1.1
   netmask 255.255.255.0
   mtu 1500

$ sudo augtool # Lets just delete eth1 now
augtool> rm /files/etc/network/interfaces/iface[. = 'eth1']
rm : /files/etc/network/interfaces/iface[. = 'eth1'] 6  <-- 6="" augtool="" fields="" removed=""> save
Saved 1 file(s)
augtool> 

Now, the same/similar exercise programmatically:

// Test CRUD of an iface in /etc/network/interfaces ( on Ubuntu)
// using the Augeas lib
// Aniruddha. A (aniruddha.a@gmail.com)
// Nov 1, 2014
#include <stdio.h>
#include <assert.h>
#include <stdarg.h>
#include <stddef.h>
#include <limits.h>
#include <string.h>
#include <augeas.h>
// using the .so installed from libaugeas-dev :
// gcc -I /usr/include/libxml2/ augparse.c -laugeas
//
// to run, just install libaugeas0 ( minus dev files )
#define LENS_PATH "/usr/share/augeas/lenses/dist/"
void if_create (augeas *aug, const char *ifname, int nfields, ...)
{
char key[128] = {0}, val[128] = {0};
char *p = NULL;
char *kv = NULL;
va_list ap;
int ret, i;
char iffield_path[256]= {0};
assert(aug && ifname && (nfields > 0));
ret = aug_set(aug, "/files/etc/network/interfaces/iface[last()+1]", ifname);
assert(ret == 0);
va_start(ap, nfields);
for (i = 0; i < nfields; i++) {
kv = va_arg(ap,char*);
p = strchr(kv, '=');
if (!p) continue;
strncpy(key, kv, (ptrdiff_t)(p - kv));
key[p - kv] = '\0';
strcpy(val, p + 1);
sprintf(iffield_path, "/files/etc/network/interfaces/iface[last()]/%s",
key);
ret = aug_set(aug, iffield_path, val);
assert(ret == 0);
}
va_end(ap);
}
void if_update (augeas *aug, const char *ifname, int nfields, ...)
{
char key[128] = {0}, val[128] = {0};
char *p = NULL;
char *kv = NULL;
va_list ap;
int ret, i;
char iffield_path[256]= {0};
assert(aug && ifname && (nfields > 0));
va_start(ap, nfields);
for (i = 0; i < nfields; i++) {
kv = va_arg(ap,char*);
p = strchr(kv, '=');
if (!p) continue;
strncpy(key, kv, (ptrdiff_t)(p - kv));
key[p - kv] = '\0';
strcpy(val, p + 1);
sprintf(iffield_path, "/files/etc/network/interfaces/iface[. = '%s']/%s",
ifname, key);
ret = aug_set(aug, iffield_path, val);
assert(ret == 0);
}
va_end(ap);
}
void if_rm (augeas *aug, const char *ifname)
{
int ret;
char ifpath[256]= {0};
assert(aug && ifname);
sprintf(ifpath, "/files/etc/network/interfaces/iface[. = '%s']", ifname);
ret = aug_rm(aug, ifpath);
assert(ret != -1);
}
// TEST
int main (int argc, char *argv[])
{
augeas *aug = NULL;
aug = aug_init("/", LENS_PATH, AUG_SAVE_BACKUP|AUG_NO_ERR_CLOSE);
if (!aug || (aug_error(aug) != AUG_NOERROR)) {
fprintf(stderr, "Init failed: %s\n", aug_error_message(aug));
exit(1);
}
if_create(aug, "eth2", 5,
"family=inet", "method=static",
"address=1.1.1.1", "netmask=255.255.0.0",
"mtu=1500" );
aug_save(aug);
system("cat /etc/network/interfaces");
if_update(aug, "eth2", 1, "address=2.2.2.2");
aug_save(aug);
system("cat /etc/network/interfaces");
if_rm(aug, "eth2");
aug_save(aug);
system("cat /etc/network/interfaces");
aug_close(aug);
return 0;
}
view raw augparse.c hosted with ❤ by GitHub