t’as fait de l’autoQUOI ??

Tags: , ,
2 Comments »

Ceux qui me connaissent et qui ont lu le post précédent ont peut-être cru à l’imposture: “. Basculement vers autoconf pour la génération du Makefile”

iMil ? c’est bien toi ?

Oui alors attention hein, pas d’affolement. Il s’agit réellement d’une utilisation très très light de ces outils que j’évite habituellement comme la peste. Tellement light que seul autoconf m’a été utile.

Mais reprenons. Ceux qui ont eu le courage de se plonger dans le code de pkgin se rappellent peut-être de ce genre de choses :

.if ${OPSYS} != "Linux"
LDADD+=         -lssl
.endif
.if ${OPSYS} == "Darwin" || ${OPSYS} == "SunOS"
LDADD+=         -lcrypto
.endif
.if ${OPSYS} == "DragonFly"
LDADD+=         -lutil
.endif
.if ${OPSYS} == "SunOS" || ${OPSYS} == "Linux"
LDADD+=         -lnbcompat
.endif
.if ${OPSYS} == "SunOS"
LDADD+=         -lnsl -lsocket -lresolv
LDADD+=         -Wl,-R -Wl,${LOCALBASE}/lib -L${LOCALBASE}/lib -lsqlite3
.else
LDADD+=         -Wl,-rpath -Wl,${LOCALBASE}/lib -L${LOCALBASE}/lib -lsqlite3
.endif

etc etc etc…

Ces petites plaisanteries, c’est mignon lorsqu’on adresse une ou deux plateformes, mais à la longue, ça devient parfaitement inmaintebable. Et c’est là qu’intervient autoconf; plutot que de tester le système sur lequel nous oeuvrons puis déduire quelle librairie / header inclure, nous allons plutot tester l’existence de fonctions particulières au sein du système visé.

La première chose à faire, c’est d’utiliser la commande autoscan. Cette dernière créera un fichier configure.scan, soit une sorte de template destiné à devenir configure.ac. Dans ce template, des tests assez basiques sur l’existence de certains headers ou autres fonctions sont présents.

Exemple simple, la directive AC_CHECK_LIB permet de verifier si une fonction fait ou pas partie d’une librairie. Cette macro prend en premier paramètre le nom de la fonction recherchée, en second la librairie dans laquelle rechercher, le 3ème parametre permet d’affecter une action en cas de succès, le 4eme en cas d’échec et enfin le dernier d’ajouter des librairies au test. Fort heureusement, dans la plupart des cas, nous n’utiliserons pas plus de trois paramètres :

AC_SEARCH_LIBS([sqlite3_open], [sqlite3],,
        AC_MSG_ERROR(SQLite3 not found.)
)

Dans l’exemple ci-dessus, on verifie que la fonction sqlite3_open existe bien dans la librairie libsqlite3, si tel n’est pas le cas -en particulier si la librairie elle même n’est pas présente ou pas trouvée-, nous arrêterons le script de configuration et afficherons un message d’erreur.
Notez que la macro AC_SEARCH_LIBS aura pour effet, en cas de succès, d’ajouter à la variable LIBS la librairie testée.

Et c’est bien là tout l’intérêt de la chose, car ce que nous souhaitons, c’est bénéficier d’un fichier Makefile générique qui se remplira des librairies-dépendances. En l’occurrence, c’est le fameux Makefile.in qui remplit cette fonction. En effet, à l’issue du non moins fameux configure, le fichier Makefile.in est complété avec les variables détéctées et le résultat publié dans un Makefile classique.
La bonne nouvelle, c’est que le Makefile.in peut tout à fait être sous la forme d’un BSD Makefile.

Voici par exemple un LDADD qui serait totalement complété par autoconf :

LDADD+=	@LIBS@

Il y a des dizaines, des centaines, des milliers de tutoriaux sur les autotools un peu partout sur le web, mais à mon humble avis une bonne partie d'entre eux brillent par leur faculté à rendre ces outils aussi repoussants qu'imbuvables. Par chance, je suis tombé sur deux liens qui m'ont grandement aidé à comprendre ce que je faisais: le premier est un des configure.ac les mieux foutus qu’il m’ait été donné de voir, il s’agit de celui d’apachetop, et il est truffé de bons exemples.
le second ressemble à un slideware mais regroupe de manière synthetique des informations d’habitude noyées dans 500 pages de documentation soporifiques. On notera en particulier le slide 12 qui liste les principales variables substituées.

Un dernier exemple relatif à la substitution de variables :

# check for humanize_number
AC_CHECK_FUNC([humanize_number],,
        # in DragonFly humanize_number is in libutil
        AC_SEARCH_LIBS([humanize_number], [util],,
                # don't have it, include humanize_number.c to SRCS
                [SRCS="humanize_number.c $SRCS"]
        )
)
AC_SUBST(SRCS)

Celui-ci est un tout petit peu plus complexe. Dans l’ordre, nous vérifions si la fonction humanize_number est présente dans la libc, si oui, nous ne faisons rien, autrement (notez les deux virgules), nous cherchons dans la libutil qui heberge cette fonction sous DragonFly BSD. Finalement, si nous ne trouvons toujours pas cette fonction, nous ajoutons le fichier humanize_number.c à la variable SRCS.
Cette variable n’étant pas automatiquement remplacée dans le fichier Makefile.in, nous indiquons à autoconf qu’il devra effectuer cette substitution grace à la macro AC_SUBST.

Finalement, à l’issue de l’ecriture du configure.ac et du Makefile.in associé, nous invoquons autoconf qui génèrera un script configure. L’execution de ce dernier produira un Makefile adapté à votre plateforme cible.

Convenient isn’t it ?

snprintf(surprise, BUFSIZ, “prout%s”, surprise);

Tags: , ,
No Comments »

Cet après midi, j’ai eu une mauvaise surprise. Je fus en effet étonné de constater qu’en compilant pkgin sous GNU/Linux, les appels du type :

snprintf(a, size, "unechaine/%s", a);

tronquaient a avec uniquement unechaine.

Evidemment, mon premier reflexe fut de blâmer GNU/Linux puisque ce code passait sans aucun soucis sur NetBSD, DragonFly BSD et même Solaris. Et pourtant. C’est gl qui m’informa que ce document issu de l’ISO et l’IEC explique clairement que le fait d’appeler snprintf() de la sorte rendait le résultat “imprévisible”. Dont acte.

Ainsi, j’ai imaginé la parade suivante :

Faire renvoyer le resultat malloc‘é du format string

char *
safe_snprintf(int size, const char *fmt, ...)
{
    char *p;
    va_list ap;

    XMALLOC(p, size * sizeof(char));
    va_start(ap, fmt);
    (void) vsnprintf(p, size, fmt, ap);
    va_end(ap);

    return p;
}

Puis simuler le précédent appel à snprintf via un define qui s’occuppera de libérer la destination et la faire pointer vers la chaine allouée précédemment renvoyée par safe_printf().

#define XSNPRINTF(dst, size, fmt...)            \
    do {                                        \
        char *pdst;                             \
        pdst = safe_snprintf(size, fmt);        \
        XFREE(dst);                             \
        dst = pdst;                             \
} while (/* CONSTCOND */ 0)

Je teste ce workaround en ce moment, et le résultat semble probant.

la magie des SLIST

Tags: ,
No Comments »

Il y a 5 ans de cela, je vous entretenais sur la beauté des SLIST, un concept issu du monde BSD permettant de facilement manipuler des listes chainées dans votre code C sans avoir à réinventer la roue à chaque code.

Comme je n’avais plus manipulé ce type d’objet depuis un certain temps, je me suis replongé dans ce petit tutoriel, mais je lui ai trouvé un manque que je m’apprète à combler ici même.

La manipulation des SLIST est très aisée lorsque l’on a la possibilité de déclarer sa liste de manière globale, il suffit alors, dans chaque fonction, d’appeler la “tête de liste”, souvent appelée head, via les multiples macros présentes dans queue.h.
J’ai ecrit, toujours dans la documentation sus-citée, un minuscule passage sur la façon d’utiliser les SLIST avec les macros SLIST_FIRST et SLIST_NEXT, mais je m’aperçois qu’on ne se rend pas compte de leur utilité.

Prenons le cas suivant, je veux créer une liste chainée de type SLIST dans une fonction. Je souhaite pouvoir ballader cette liste de fonctions en fonctions, et evidemment, la libérer à la fin de mes travaux. Nous utiliserons notre liste de la sorte :

/* headers et diverses déclarations */
#include <sys/queue.h>

typedef struct Mastruct {
	int id;
	char *element;
	SLIST_ENTRY(, Mastruct) next;
} Mastruct;

/* plein de trucs */

Mastruct *
initialisation(int id, const char *str)
{
	Mastruct *mesdatas;

	/* on déclare la tête de liste */
	SLIST_HEAD(, Mastruct) datahead;
	/* on l'initialise à NULL */
	SLIST_INIT(&datahead);

	if ((mesdatas = malloc(sizeof(Mastruct)) == NULL)
		err("malloc error");

	mesdatas->id = id;
	if (str != NULL) {
		mesdatas->element = strdup(str);
	} else {
		mesdatas->element = NULL;
	}

	/* on accroche la structure fraichement allouée à la tête de liste */
	SLIST_INSERT_HEAD(&datahead, mesdatas, next);

	/* et on renvoie le premier élément de datahead, ici, mesdatas*/
	return SLIST_FIRST(&datahead);
}

Dans ce cas de figure, nous ne renvoyons pas la tête de la liste mais simplement le premier élément de la liste chainée. Pour parcourir simplement cette liste, nous utiliserons la macro SLIST_NEXT :

	Mastruct *mesdatas, *p;

	/* des trucs */

	mesdatas = initialisation(id, unechaine);

	for (p = mesdatas; p != NULL; p = SLIST_NEXT(p, next))
		printf("%d -> %s", p->id, p->element);

De la même façon, on pourra simplement libérer cette liste simplement chainée grace à une boucle de ce type :

void
free_datas(Mastruct *mesdatas)
{
	struct Mastruct *p;

	/* on boucle tant que le nouveau chainon n'est pas nul */
	while (mesdatas != NULL) {
		/* on sauvegarde le prochain chainon */
		p = SLIST_NEXT(mesdatas, next);
		/* on libère le contenu */
		free(mesdatas->element);
		/* puis le chainon courant */
		free(mesdatas);
		/* puis on fait pointer la variable principale vers la sauvegarde */
		mesdatas = p;
	}
}

Désormais, avec un effort minimum, nous voici en mesure de très simplement créer des listes de données rapides et dynamiques.

char *slurp(const char *url)

Tags: ,
No Comments »

Pour mon projet top secret que j’ai, j’avais besoin de télécharger un fichier, en C, via FTP ou HTTP. En cherchant ce qui est fait dans pkg_install, et plus particulièrement dans admin/audit.c, j’ai trouvé un bon exemple d’utilisation de la libfetch, dont le man est à mon avis complètement indigeste.

Petit résumé.

Afin de visualiser un code complet et fonctionnel sur l’utilisation de la libfetch, le fichier audit.c précedemment cité fera reference.

Considérons une fonction, fetch_url qui prendra en paramètre une URL. Afin d’utiliser la libfetch, nous auront besoin du header fetch.h. Voici donc le début de notre fichier contenant la fonction fetch_url :

#include <fetch.h>

void
fetch_url(const char *url)

La première fonction que nous allons utiliser se nomme fetchXGetURL(), elle permettra de valider que l’url passée en paramètre est bien de la forme proto://hostname/peut-etre-quelque-chose, elle renverra une sorte de descripteur, de type fetchIO et placera la taille du fichier visé dans la variable st, de type struct url_stat, le 3eme parametre représente les flags eventuels, nous n’en passons pas :

	struct url_stat st;
	fetchIO *f;

	if ((f = fetchXGetURL(url, &st, "")) == NULL) {
		/* gérez l'erreur */
	}

Une fois le descripteur ouvert et les informations sur la cible récupérées dans la variable st, nous pouvons préparer le conteneur, un pointeur de type char * dont nous pouvons d’ores et déjà reserver la taille :

    buf_len = st.size;
    if ((buf = malloc(buf_len + 1)) == NULL)
        err(EXIT_FAILURE, "malloc failed");

On aura bien entendu préalablement déclaré les variables buf_len et buf :

	char *buf;
	size_t buf_len;

On n’oubliera evidemment pas de libérer buf lorsque l’on en aura plus besoin grace à un free(buf).

Arrive la boucle principale, qui bouclera tant que la taille de ce qui est lu + ce qui a déjà été lu est inferieure à la taille présente dans st.size, soit buf_len. La fonction issue de la libfetch en charge de la lecture se nomme fetchIO_read() :

    while (buf_fetched < buf_len) {
        cur_fetched = fetchIO_read(f, buf + buf_fetched,
            buf_len - buf_fetched);
        if (cur_fetched == 0)
            errx(EXIT_FAILURE, "truncated file");
        else if (cur_fetched == -1)
            errx(EXIT_FAILURE, "Failure during fetch: %s",
                fetchLastErrString);
        buf_fetched += cur_fetched;
    }

Et voila ! la variable buf contient maintenant le resultat de la lecture de l'url const char *url, à vous d'inventer le reste :)

La magie de l’ELF

Tags: , ,
1 Comment »

En comparant le projet qui m’obsède jour et nuit et les outils similaires déjà existants (crunchgen(1), rescue), j’en suis venu à me poser des questions sur le cas d’un mode “full static”. Pour mémoire, BeastieBox “trouve” la fonction à appeler fonction de argv[0], et je pars du principe que chaque nom de fonction doit être construit dynamiquement. Ce tour de passe-passe est facilement geré par les fonctions dlopen(3) / dlsym(3) dans le cas d’un binaire compilé dynamiquement, mais quid d’un binaire beastiebox compilé à l’aide du swtich -static ? Point de ld.so_elf à la rescousse ici, la seule solution qui m’est apparue… c’est d’attaquer le binaire comme le fait précisemment ld.so_elf, en mmap‘ant le binaire et en l’adressant via les structures ELF.

J’ai posté l’explication dans la Mollacademy, et le code associé de BeastieBox est visible ici, ici et .

Duplication de symboles, la bonne methode

Tags: ,
1 Comment »

On oublie le post ci dessous. En effet, après avoir posté le resultat de mes travaux sur tech-userlevel@, j’ai appris une astuce des plus magiques. Arnaud Lacombe me dit dans une réponse :

Just looking quickly at the code, you can avoid the “#ifdef BBOX commant_() #else main() #endif” heavy logic and the `commonfunc.sh’ hack by using nm(1), objcopy(1) and symbol renaming as done by crunchgen(1).

Je regarde donc comment s’y prend le gaillard de crunchgen(1) pour eviter les conflits de symboles et je vois ceci :

        ${NM} -ng cat/cat.ro | awk '/^ *U / { next }; /^[0-9a-fA-F]+ C/ { next }; / main$$/ { print "main _crunched_cat_stub"; next }; { print $3 " " $3 "$$from$$cat" }' > cat.cro.syms
${OBJCOPY} --redefine-syms cat.cro.syms cat/cat.ro cat.cro

Et ça, ça va m’apprendre à lire les manpages en entier.

En clair, à l’aide de nm(1), on liste les symboles exportés par les objets, on leur associe un nouveau nom avec awk(1) puis on redefinit les noms des symboles en passant à objcopy(1) le fichier de correspondances fraichement créé.

Magnifique.

WP Theme & Icons based on GlossyBlue by N.Design Studio
Banner from www.trynthlas.com
Entries RSS Comments RSS Log in