Et si on oubliait les bases ?
Par Florent Bruneau le samedi 26 mai 2007, 11:40:00 - GeekTime - Lien permanent
MacOS X est un Unix... compatible POSIX. Voilà ce qu'un certain nombre de personnes semblent oublier assez fréquemment. C'est assez dommage quand ces personnes programment pour MacOS, on se retrouve parfois avec du code complexe pour réécrire des fonctions POSIX (en moins bien ?).
En fait si je parle de ça, c'est parce que je viens de rencontrer le problème dans le cadre du développement de VirtueDesktops. Il se trouve qu'en parcourant le tracker du projet je suis tombé sur le ticket 115 qui s'intitule :
Patch to check group id of 'procmod' group
C'est intéressant... jusqu'à maintenant Virtue suppose que le gid du groupe procmod est celui par défaut de MacOS (c'est à dire qu'il vaut 9)... et le problème est donc que si un utilisateur a un procmod différent (ou si Apple décide un jour de changer le gid du groupe), le code actuel peut avoir des résultats inattendus, il faut donc faire un code plus portable qui recherche le gid de procmod au lieu de le stocker en dur. Ce n'est clairement pas une mauvaise idée ! Seul problème, le patch soumis est le suivant (au cas où certain voudrait réutiliser ce code, je tiens à signaler que c'est une mauvaise idée !) :
[c]
#define NI_DOMAIN "."
#define NI_PATH "/name=groups/name=procmod"
#define NI_KEY "gid"
// Sucked from netinfo-369.5/tools/niutil/niutil.c
ni_status ni_read_single_prop(char **property)
{
const char *args[] = {NI_DOMAIN, NI_PATH, NI_KEY};
char myname[] = "ni_read_single_prop";
const bool opt_tag = false;
const int timeout = 30;
ni_namelist nl;
void *domain;
ni_id dir;
ni_status ret;
if ((ret = do_open(myname, args[0], &domain, opt_tag, timeout, NULL, NULL)) != 0) return ret;
/* args[1] should be a directory specification */
ret = ni2_pathsearch(domain, &dir, args[1]);
if (ret != NI_OK) {
fprintf(stderr, "%s: can't open directory %s: %s
", myname, args[1], ni_error(ret));
ni_free(domain);
return ret;
}
/* get the property values for args[2] */
NI_INIT(&nl);
ret = ni_lookupprop(domain, &dir, args[2], &nl);
if (ret != NI_OK) {
fprintf(stderr, "%s: can't get property %s in directory %s: %s
", myname, args[2], args[1], ni_error(ret));
ni_free(domain);
return ret;
}
if (nl.ni_namelist_len != 1) {
fprintf(stderr, "%s: expected length = 1, found length = %d
", myname, nl.ni_namelist_len);
return NI_FAILED;
}
*property = (char*)calloc(strlen(nl.ni_namelist_val[0]) + 1, sizeof(char));
strcpy(*property, nl.ni_namelist_val[0]);
ni_namelist_free(&nl);
ni_free(domain);
return NI_OK;
}
Donc que fait cette fonction ? C'est assez simple : elle utilise l'utilitaire NetInfo d'Apple pour lire les informations relatives au chemin /groups/procmod. Une fois là dedans elle copie la valeur stockée à la clé gid et renvoie cette chaîne dans le pointeur passé en argument.
Il n'y a pas à dire... c'est une solution qui devrait fonctionner (enfin j'ai même un doute, car je ne vois pas le groupe procmod apparaître quand je regarde dans NetInfo). Seulement cette solution montre clairement que la personne qui l'a écrite savait que MacOS utilise un système de groupes, mais avait oublié qu'en fait c'est avant tout un système POSIX. Et là, ce qui est intéressant c'est que sous MacOS X on a accès à l'API POSIX... en particulier à la fonction getgrnam qui retourne les informations sur un groupe à partir de son nom.
Donc, pas besoin de dépendance vers NetInfo, pas besoin d'une recherche dans une arborescence abstraite : NetInfo n'est qu'une abstraction de la couche Unix pour simplifier l'accès aux données par les utilisateurs... mais ça ne doit pas être un framework d'abstraction de l'API POSIX ce qui entraîne nécessairement du code moins portable (et surtout, moins lisible dans le cas présent), moins rapide et plus lourd.
Ici, le code est utilisé dans un programme en C qui est chargé de mettre un objet dans le groupe procmod pour autoriser Virtue à effectuer certaines actions qui nécessitent un accès privilégié au serveur graphique... ce programme ne fait que changer les permissions. Pourquoi utiliser une API lourde et spécifique à MacOS alors que ce type de programme pourrait clairement être utilisé sur n'importe quel Unix ?
Voici donc une solution qui fait la même chose que la fonction ci-dessus, en mieux et surtout en beaucoup moins de lignes :
[c]
#include <grp.h>
static int getProcmodGid()
{
struct group* groupDesc;
int gid = -1;
groupDesc = getgrnam("procmod");
if (groupDesc) {
gid = groupDesc->gr_gid;
free(groupDesc);
}
return gid;
}
Pourquoi cette solution est-elle meilleure ?
- elle retourne un entier... on a donc pas besoin de faire une conversion a posteriori
- elle utilise l'API POSIX et est donc utilisable sur n'importe quel OS POSIX (donc quasiment tous... à l'exception de Windows)
- elle évite toute manipulation inutile de chaîne de caractères
- elle est ne nécessite pas de charger une bibliothèque supplémentaire :
getgrnamest dans lalibc - elle est drôlement plus courte non ?
Commentaires
La base NetInfo a des comportements étranges parfois cependant : elle supplante les fichiers de conf dans /etc/, etc. A mon avis, il est probable que ''getgrnam'', derrière les rideaux, accède à la base NetInfo, non ?
Hum, je ne pense pas que getgrnam utilise NetInfo. getgrnam est une fonction de la libc, je ne pense pas qu'il soit envisageable de trouver une implémentation de la libc qui dépende de frameworks de plus haut niveau.
De plus, comme indiqué, procmod n'est pas dans la liste des groupes dans l'utilitaire NetInfo alors qu'il est dans mon /etc/groups (et que j'y accède avec getgrnam), ce qui me laisse penser que NetInfo se situe à une couche supérieure après un filtrage.
Après un certain nombre de recherche dans les sources de Darwin, je tends à penser que getgrnam est un appel transmis directement au noyau par la libc (je n'en trouve pas l'implémentation en fait).
getgrnam utilise la la base NetInfo. (idem pour les autres fonctions du même genre, ou getaddrbyname qui effectue des requêtes DNS ou mDNS).
En regardant le code, getgrnam appelle getgr qui appelle getgr_internal. getgr_internal utilise les appels aux fonctions _lookup_* qui permet de communiquer avec le démon lookupd qui lui se utilise tous les moyens à sa disposition pour faire la résolution du nom de groupe (fichiers plats dans /etc, base netinfo, ldap... cf le man de lookupd et l'outil "Format de Répertoire").
L'équivalent de la "libc" sous Mac OS X est la "libSystem“. Elle contient entre autre toutes les fonctions liées à NetInfo.
J'ai passé pas mal de temps à lire les sources disponible sur le site d'Apple... et bizarrement le package des sources de la libSystem ne contenait rien d'autre que des références à des noms de fonction sans aucune implémentation !
La libc par contre contient beaucoup de code... donc il faudrait qu'on m'explique comme un truc plein peut dépendre d'un truc vide.
En fait, je n'ai pas regardé les sources, j'ai juste regardé les binaires. (à coups de otool, nm et gdb, ça se fait bien)
On voit que la libSystem est (quasi) autonome, c'est-à-dire pas de dépendances avec d'autres lib partagées. (otool -L /usr/lib/libSystem.dylib). Apple a dû mixer les libs de base ensemble dans libSystem, mais garder la séparation logique au niveau des sources.
Sinon, si tu suspends lookupd (killall -STOP lookupd), les outils comme id qui font des résolutions uid/nom se bloquent.