/* 15May24: Eine Implementierung einer execlp(2)-artigen Funktion.
 * Die Absicht ist hier zu zeigen, wie execlp(2) auf execl(2)
 * reduziert werden kann. */

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/* Wir brauchen hier die Schnittstelle, damit wir beim übersetzen
 * immer sicher sein können, dass wir in diesem Modul uns auch an
 * unsere eigenen Versprechen halten. */
#include "exec1.h"

/* Die Idee ist es einen Spezialfall von execlp(2) anzubieten wenn man
 * nur ein Argument hat, OHNE selbst execlp(2) zu benutzen.  (Zur
 * Erinnerung: execl(2) führt ein Programm aus welches man mit einem
 * Pfad angeben hat.  execlp(2) sucht nach dem Programm im Systempfad
 * (Liste von Verzeichnissen mit Programmen) nach).
 *
 * Beachte: diese Funktion ist nicht `static', denn es soll auch aus
 * dem anderen Modul aufrufbar sein. */
void exec1(char *cmd, char *arg) {
     /* Unix hat das Konzept von "Umgebungsvariablen".  Diese werden
      * von Kind-Prozessen vererbt, und sollen dazu diesen
      * Systeminformation weiterzugeben.  Hier interessiert uns die
      * Variable PATH, wo Verziechnisse aufgezählt werden, wo man
      * Anwendungen finden kann. */

     char *PATH = getenv("PATH"); /* ~~> /usr/bin:/bin:/usr/local/bin */
     if (PATH == NULL) {
          return;
     }

     /* Der PATH ist mit Doppelpunkten (:) getrennt.  Um diese
      * nacheinander abzurufen, wollen wir die Bestandteile
      * nacheinander ausprobieren: Dazu benutzen wir die Funktion
      * `strtok', welche den String in seine Bestandteile Trennt.  Bei
      * jedem Aufruf bekommen wir ein "Tücken" (Einheit). */
     char *dir = strtok(PATH, ":");
     while (dir != NULL) {

          /* Wenn wir Verzeichnis haben (und den Programm-Namen aus
           * `cmd'), können wir mit sprintf(3) das kombinieren zu einem Pfad:*/
          char path[strlen(dir) + 1 + strlen(cmd) + 1];
          sprintf(path, "%s/%s", dir, cmd);

          /* Diesen absoluten Pfad übergeben wir nun an execl(2), was
           * den Tatsächlichen Systemaufruf absetzt, und versucht das
           * neue Programm zu starten. */
          execl(path, cmd, arg, NULL);

          /* Sollte der Aufruf gerade nicht geklappt haben, gab es die
           * Datei nicht.  Daher probieren wir den nächsten Token aus,
           * indem wir strtok mit NULL aufrufen.  Damit wird es von
           * dem Punkt an im String arbeiten, wo es das letzte mal
           * aufgehört hat, und den nächsten Token generieren.  Die
           * Trennzeichen (übergeben als String, was als Menge von
           * Zeichen zu interpretieren ist) kann zwischen Aufrufen
           * variiert werden.  */
          dir = strtok(NULL, ":");
     }
}

/* Bonus: Wir können seit C99 auch execl auf execv reduzieren, indem
 * man Makro-Definitionen benutzt: */

#define execl(cmd, ...)         execv(cmd, (char*[]) { __VA_ARGS__ })

/* Man kann daher sagen, dass execv(2) die Grundlegendste Funktion
 * ist: execl(2) und execvp(2) bauen auf dieser auf, genau wie oben
 * gesehen wurde, wie execlp(2) auf execl(2) aufbaut:
 *
 *            execlp  exec1
 *               \     /
 *                \   /
 *                execl  execvp
 *                  \     /
 *                   \   /
 *                   execv
 */