First rough-cut version of scriptlet abstraction
authorPanu Matilainen <pmatilai@redhat.com>
Thu, 25 Feb 2010 08:58:51 +0000 (10:58 +0200)
committerPanu Matilainen <pmatilai@redhat.com>
Thu, 25 Feb 2010 08:58:51 +0000 (10:58 +0200)
- Split the low-level scriptlet machinery out of psm
- New struct to hold the necessary information about scriptlets so
  we can execute them without having a header at hand.
- Trigger handling is hackish and needs more love...

lib/Makefile.am
lib/psm.c
lib/psm.h
lib/rpmscript.c [new file with mode: 0644]
lib/rpmscript.h [new file with mode: 0644]

index af264a2..fb7e7bf 100644 (file)
@@ -32,7 +32,7 @@ librpm_la_SOURCES = \
        rpmte.c rpmte_internal.h rpmts.c \
        rpmvercmp.c signature.c signature.h transaction.c \
        verify.c rpmlock.c rpmlock.h misc.h \
-       legacy.c merge.c \
+       rpmscript.h rpmscript.c legacy.c merge.c \
        rpmliblua.c rpmliblua.h
 
 librpm_la_LDFLAGS = -version-info 1:0:0
index bc56767..269407b 100644 (file)
--- a/lib/psm.c
+++ b/lib/psm.c
@@ -9,16 +9,14 @@
 
 #include <rpm/rpmlib.h>                /* rpmvercmp and others */
 #include <rpm/rpmmacro.h>
-#include <rpm/rpmurl.h>
 #include <rpm/rpmds.h>
 #include <rpm/rpmts.h>
-#include <rpm/rpmfileutil.h>   /* rpmMkTemp() */
+#include <rpm/rpmfileutil.h>
 #include <rpm/rpmdb.h>         /* XXX for db_chrootDone */
 #include <rpm/rpmlog.h>
 #include <rpm/rpmstring.h>
 #include <rpm/argv.h>
 
-#include "rpmio/rpmlua.h"
 #include "lib/cpio.h"
 #include "lib/fsm.h"           /* XXX CPIO_FOO/FSM_FOO constants */
 #include "lib/psm.h"
@@ -26,6 +24,7 @@
 #include "lib/rpmte_internal.h"        /* XXX internal apis */
 #include "lib/rpmlead.h"               /* writeLead proto */
 #include "lib/signature.h"             /* signature constants */
+#include "lib/rpmscript.h"
 
 #include "debug.h"
 
@@ -350,32 +349,6 @@ exit:
     return rpmrc;
 }
 
-static const char * const SCRIPT_PATH = "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin";
-
-/**
- * Return scriptlet name from tag.
- * @param tag          scriptlet tag
- * @return             name of scriptlet
- */
-static const char * tag2sln(rpmTag tag)
-{
-    switch ((rpm_tag_t) tag) {
-    case RPMTAG_PRETRANS:       return "%pretrans";
-    case RPMTAG_TRIGGERPREIN:   return "%triggerprein";
-    case RPMTAG_PREIN:          return "%pre";
-    case RPMTAG_POSTIN:         return "%post";
-    case RPMTAG_TRIGGERIN:      return "%triggerin";
-    case RPMTAG_TRIGGERUN:      return "%triggerun";
-    case RPMTAG_PREUN:          return "%preun";
-    case RPMTAG_POSTUN:         return "%postun";
-    case RPMTAG_POSTTRANS:      return "%posttrans";
-    case RPMTAG_TRIGGERPOSTUN:  return "%triggerpostun";
-    case RPMTAG_VERIFYSCRIPT:   return "%verify";
-    default: break;
-    }
-    return "%unknownscript";
-}
-
 static rpmTag triggertag(rpmsenseFlags sense) 
 {
     rpmTag tag = RPMTAG_NOT_FOUND;
@@ -399,295 +372,7 @@ static rpmTag triggertag(rpmsenseFlags sense)
 }
 
 /**
- * Run internal Lua script.
- */
-static rpmRC runLuaScript(rpmts ts, ARGV_const_t prefixes,
-                  const char *sname, rpmlogLvl lvl,
-                  ARGV_t * argvp, const char *script, int arg1, int arg2)
-{
-    rpmRC rc = RPMRC_FAIL;
-#ifdef WITH_LUA
-    int rootFd = -1;
-    int xx;
-    ARGV_t argv = argvp ? *argvp : NULL;
-    rpmlua lua = NULL; /* Global state. */
-    rpmluav var;
-
-    rpmlog(RPMLOG_DEBUG, "%s: running <lua> scriptlet.\n", sname);
-    if (!rpmtsChrootDone(ts)) {
-       const char *rootDir = rpmtsRootDir(ts);
-       xx = chdir("/");
-       rootFd = open(".", O_RDONLY, 0);
-       if (rootFd >= 0) {
-           if (rootDir != NULL && !rstreq(rootDir, "/") && *rootDir == '/')
-               xx = chroot(rootDir);
-           xx = rpmtsSetChrootDone(ts, 1);
-       }
-    }
-
-    /* Create arg variable */
-    rpmluaPushTable(lua, "arg");
-    var = rpmluavNew();
-    rpmluavSetListMode(var, 1);
-    if (argv) {
-       char **p;
-       for (p = argv; *p; p++) {
-           rpmluavSetValue(var, RPMLUAV_STRING, *p);
-           rpmluaSetVar(lua, var);
-       }
-    }
-    if (arg1 >= 0) {
-       rpmluavSetValueNum(var, arg1);
-       rpmluaSetVar(lua, var);
-    }
-    if (arg2 >= 0) {
-       rpmluavSetValueNum(var, arg2);
-       rpmluaSetVar(lua, var);
-    }
-    var = rpmluavFree(var);
-    rpmluaPop(lua);
-
-    if (rpmluaRunScript(lua, script, sname) == 0) {
-       rc = RPMRC_OK;
-    }
-
-    rpmluaDelVar(lua, "arg");
-
-    if (rootFd >= 0) {
-       const char *rootDir = rpmtsRootDir(ts);
-       xx = fchdir(rootFd);
-       xx = close(rootFd);
-       if (rootDir != NULL && !rstreq(rootDir, "/") && *rootDir == '/')
-           xx = chroot(".");
-       xx = rpmtsSetChrootDone(ts, 0);
-    }
-#else
-    rpmlog(lvl, _("<lua> scriptlet support not built in\n"));
-#endif
-
-    return rc;
-}
-
-static void doScriptExec(rpmts ts, ARGV_const_t argv, ARGV_const_t prefixes,
-                       FD_t scriptFd, FD_t out)
-{
-    const char * rootDir;
-    int pipes[2];
-    int flag;
-    int fdno;
-    int xx;
-    int open_max;
-
-    (void) signal(SIGPIPE, SIG_DFL);
-    pipes[0] = pipes[1] = 0;
-    /* make stdin inaccessible */
-    xx = pipe(pipes);
-    xx = close(pipes[1]);
-    xx = dup2(pipes[0], STDIN_FILENO);
-    xx = close(pipes[0]);
-
-    /* XXX Force FD_CLOEXEC on all inherited fdno's. */
-    open_max = sysconf(_SC_OPEN_MAX);
-    if (open_max == -1) {
-       open_max = 1024;
-    }
-    for (fdno = 3; fdno < open_max; fdno++) {
-       flag = fcntl(fdno, F_GETFD);
-       if (flag == -1 || (flag & FD_CLOEXEC))
-           continue;
-       xx = fcntl(fdno, F_SETFD, FD_CLOEXEC);
-       /* XXX W2DO? debug msg for inheirited fdno w/o FD_CLOEXEC */
-    }
-
-    if (scriptFd != NULL) {
-       int sfdno = Fileno(scriptFd);
-       int ofdno = Fileno(out);
-       if (sfdno != STDERR_FILENO)
-           xx = dup2(sfdno, STDERR_FILENO);
-       if (ofdno != STDOUT_FILENO)
-           xx = dup2(ofdno, STDOUT_FILENO);
-       /* make sure we don't close stdin/stderr/stdout by mistake! */
-       if (ofdno > STDERR_FILENO && ofdno != sfdno)
-           xx = Fclose (out);
-       if (sfdno > STDERR_FILENO && ofdno != sfdno)
-           xx = Fclose (scriptFd);
-    }
-
-    {   char *ipath = rpmExpand("%{_install_script_path}", NULL);
-       const char *path = SCRIPT_PATH;
-
-       if (ipath && ipath[5] != '%')
-           path = ipath;
-
-       xx = setenv("PATH", path, 1);
-       ipath = _free(ipath);
-    }
-
-    for (ARGV_const_t pf = prefixes; pf && *pf; pf++) {
-       char *name = NULL;
-       int num = (pf - prefixes);
-
-       rasprintf(&name, "RPM_INSTALL_PREFIX%d", num);
-       setenv(name, *pf, 1);
-       free(name);
-
-       /* scripts might still be using the old style prefix */
-       if (num == 0) {
-           setenv("RPM_INSTALL_PREFIX", *pf, 1);
-       }
-    }
-       
-    rootDir = rpmtsRootDir(ts);
-    if (rootDir  != NULL) {    /* XXX can't happen */
-       if (!rpmtsChrootDone(ts) &&
-           !(rootDir[0] == '/' && rootDir[1] == '\0'))
-       {
-           xx = chroot(rootDir);
-       }
-       xx = chdir("/");
-
-       /* XXX Don't mtrace into children. */
-       unsetenv("MALLOC_CHECK_");
-
-       /* Permit libselinux to do the scriptlet exec. */
-       if (rpmtsSELinuxEnabled(ts) == 1) {     
-           xx = rpm_execcon(0, argv[0], argv, environ);
-       }
-
-       if (xx == 0) {
-           xx = execv(argv[0], argv);
-       }
-    }
-    _exit(127); /* exit 127 for compatibility with bash(1) */
-}
-
-/**
- * Run an external script.
- */
-static rpmRC runExtScript(rpmts ts, ARGV_const_t prefixes,
-                  const char *sname, rpmlogLvl lvl,
-                  ARGV_t * argvp, const char *script, int arg1, int arg2)
-{
-    FD_t scriptFd;
-    FD_t out = NULL;
-    char * fn = NULL;
-    int xx;
-    rpmRC rc = RPMRC_FAIL;
-    struct rpmsqElem sq;
-
-    memset(&sq, 0, sizeof(sq));
-    sq.reaper = 1;
-
-    rpmlog(RPMLOG_DEBUG, "%s: scriptlet start\n", sname);
-
-    if (argvCount(*argvp) == 0) {
-       argvAdd(argvp, "/bin/sh");
-    }
-
-    if (script) {
-       const char * rootDir = rpmtsRootDir(ts);
-       FD_t fd;
-
-       fd = rpmMkTempFile((!rpmtsChrootDone(ts) ? rootDir : "/"), &fn);
-       if (fd == NULL || Ferror(fd)) {
-           rpmlog(RPMLOG_ERR, _("Couldn't create temporary file for %s: %s\n"),
-                  sname, strerror(errno));
-           goto exit;
-       }
-
-       if (rpmIsDebug() &&
-           (rstreq(*argvp[0], "/bin/sh") || rstreq(*argvp[0], "/bin/bash")))
-       {
-           static const char set_x[] = "set -x\n";
-           xx = Fwrite(set_x, sizeof(set_x[0]), sizeof(set_x)-1, fd);
-       }
-
-       xx = Fwrite(script, sizeof(script[0]), strlen(script), fd);
-       xx = Fclose(fd);
-
-       {   const char * sn = fn;
-           if (!rpmtsChrootDone(ts) && rootDir != NULL &&
-               !(rootDir[0] == '/' && rootDir[1] == '\0'))
-           {
-               sn += strlen(rootDir)-1;
-           }
-           argvAdd(argvp, sn);
-       }
-
-       if (arg1 >= 0) {
-           argvAddNum(argvp, arg1);
-       }
-       if (arg2 >= 0) {
-           argvAddNum(argvp, arg2);
-       }
-    }
-
-    scriptFd = rpmtsScriptFd(ts);
-    if (scriptFd != NULL) {
-       if (rpmIsVerbose()) {
-           out = fdDup(Fileno(scriptFd));
-       } else {
-           out = Fopen("/dev/null", "w.fdio");
-           if (Ferror(out)) {
-               out = fdDup(Fileno(scriptFd));
-           }
-       }
-    } else {
-       out = fdDup(STDOUT_FILENO);
-    }
-    if (out == NULL) { 
-       rpmlog(RPMLOG_ERR, _("Couldn't duplicate file descriptor: %s: %s\n"),
-              sname, strerror(errno));
-       goto exit;
-    }
-
-    xx = rpmsqFork(&sq);
-    if (sq.child == 0) {
-       rpmlog(RPMLOG_DEBUG, "%s: execv(%s) pid %d\n",
-              sname, *argvp[0], (unsigned)getpid());
-       doScriptExec(ts, *argvp, prefixes, scriptFd, out);
-    }
-
-    if (sq.child == (pid_t)-1) {
-       rpmlog(RPMLOG_ERR, _("Couldn't fork %s: %s\n"), sname, strerror(errno));
-       goto exit;
-    }
-
-    rpmsqWait(&sq);
-
-    rpmlog(RPMLOG_DEBUG, "%s: waitpid(%d) rc %d status %x\n",
-          sname, (unsigned)sq.child, (unsigned)sq.reaped, sq.status);
-
-    if (sq.reaped < 0) {
-       rpmlog(lvl, _("%s scriptlet failed, waitpid(%d) rc %d: %s\n"),
-                sname, sq.child, sq.reaped, strerror(errno));
-    } else if (!WIFEXITED(sq.status) || WEXITSTATUS(sq.status)) {
-       if (WIFSIGNALED(sq.status)) {
-           rpmlog(lvl, _("%s scriptlet failed, signal %d\n"),
-                   sname, WTERMSIG(sq.status));
-       } else {
-           rpmlog(lvl, _("%s scriptlet failed, exit status %d\n"),
-                  sname, WEXITSTATUS(sq.status));
-       }
-    } else {
-       /* if we get this far we're clear */
-       rc = RPMRC_OK;
-    }
-
-exit:
-    if (out)
-       xx = Fclose(out);       /* XXX dup'd STDOUT_FILENO */
-
-    if (script) {
-       if (!rpmIsDebug())
-           xx = unlink(fn);
-       fn = _free(fn);
-    }
-    return rc;
-}
-
-/**
- * Run scriptlet with args.
+ * Run a scriptlet with args.
  *
  * Run a script with an interpreter. If the interpreter is not specified,
  * /bin/sh will be used. If the interpreter is /bin/sh, then the args from
@@ -695,89 +380,50 @@ exit:
  *
  * @param psm          package state machine data
  * @param prefixes     install prefixes
- * @param stag         scriptlet section tag
- * @param argvp                ARGV_t pointer to args from header, *argvp[0] is the 
- *                     interpreter to use. Pointer as we might need to 
- *                     modify via argvAdd()
  * @param script       scriptlet from header
  * @param arg1         no. instances of package installed after scriptlet exec
  *                     (-1 is no arg)
  * @param arg2         ditto, but for the target package
  * @return             0 on success
  */
-static rpmRC runScript(rpmpsm psm, ARGV_const_t prefixes, rpmTag stag, 
-                      ARGV_t * argvp, const char * script, int arg1, int arg2)
+static rpmRC runScript(rpmpsm psm, ARGV_const_t prefixes, 
+                      rpmScript script, int arg1, int arg2)
 {
-    rpmRC rc = RPMRC_FAIL; /* assume failure */
-    char *sname = NULL; 
-    int warn_only = (stag != RPMTAG_PREIN && stag != RPMTAG_PREUN);
-    rpmlogLvl loglvl = warn_only ? RPMLOG_WARNING : RPMLOG_ERR;
-
-    if (*argvp == NULL && script == NULL)
-       return RPMRC_OK;
-
-    rasprintf(&sname, "%s(%s)", tag2sln(stag), rpmteNEVRA(psm->te));
-
-    rpmswEnter(rpmtsOp(psm->ts, RPMTS_OP_SCRIPTLETS), 0);
-    if (*argvp[0] && rstreq(*argvp[0], "<lua>")) {
-       rc = runLuaScript(psm->ts, prefixes, sname, loglvl, argvp, script, arg1, arg2);
-    } else {
-       rc = runExtScript(psm->ts, prefixes, sname, loglvl, argvp, script, arg1, arg2);
-    }
-    rpmswExit(rpmtsOp(psm->ts, RPMTS_OP_SCRIPTLETS), 0);
+    rpmRC rc = RPMRC_OK;
+    int warn_only =(script->tag != RPMTAG_PREIN && script->tag != RPMTAG_PREUN);
 
     /* 
      * Notify callback for all errors. "total" abused for warning/error,
      * rc only reflects whether the condition prevented install/erase 
      * (which is only happens with %prein and %preun scriptlets) or not.
      */
+    rc = rpmScriptRun(script, arg1, arg2, psm->ts, prefixes, warn_only);
     if (rc != RPMRC_OK) {
        if (warn_only) {
            rc = RPMRC_OK;
        }
-       rpmtsNotify(psm->ts, psm->te, RPMCALLBACK_SCRIPT_ERROR, stag, rc);
+       rpmtsNotify(psm->ts, psm->te, RPMCALLBACK_SCRIPT_ERROR, script->tag, rc);
     }
 
-    free(sname);
     return rc;
 }
 
-/**
- * Retrieve and run scriptlet from header.
- * @param psm          package state machine data
- * @return             rpmRC return code
- */
 static rpmRC runInstScript(rpmpsm psm)
 {
     rpmRC rc = RPMRC_OK;
-    ARGV_t argv;
-    struct rpmtd_s script, prog, pfx;
-    const char *str;
+    struct rpmtd_s pfx;
     Header h = rpmteHeader(psm->te);
+    rpmScript script = rpmScriptFromTag(h, psm->scriptTag);
 
-    if (h == NULL)     /* XXX can't happen */
-       return RPMRC_FAIL;
-
-    headerGet(h, psm->scriptTag, &script, HEADERGET_DEFAULT);
-    headerGet(h, psm->progTag, &prog, HEADERGET_DEFAULT);
-    if (rpmtdCount(&script) == 0 && rpmtdCount(&prog) == 0)
-       goto exit;
-
-    argv = argvNew();
-    while ((str = rpmtdNextString(&prog))) {
-       argvAdd(&argv, str);
+    if (script) {
+       headerGet(h, RPMTAG_INSTPREFIXES, &pfx, HEADERGET_ALLOC|HEADERGET_ARGV);
+       rc = runScript(psm, pfx.data, script, psm->scriptArg, -1);
+       rpmtdFreeData(&pfx);
     }
 
-    headerGet(h, RPMTAG_INSTPREFIXES, &pfx, HEADERGET_ALLOC|HEADERGET_ARGV);
-    rc = runScript(psm, pfx.data, psm->scriptTag, &argv,
-                  rpmtdGetString(&script), psm->scriptArg, -1);
-    rpmtdFreeData(&pfx);
-    argvFree(argv);
-
-exit:
-    rpmtdFreeData(&script);
-    rpmtdFreeData(&prog);
+    rpmScriptFree(script);
     headerFree(h);
+
     return rc;
 }
 
@@ -830,24 +476,29 @@ static rpmRC handleOneTrigger(const rpmpsm psm,
            continue;
        } else {
            int arg1 = rpmdbCountPackages(rpmtsGetRdb(ts), triggerName);
-           const char ** triggerScripts = tscripts.data;
-           const char ** triggerProgs = tprogs.data;
+           char ** triggerScripts = tscripts.data;
+           char ** triggerProgs = tprogs.data;
            uint32_t * triggerIndices = tindexes.data;
+           uint32_t ix = triggerIndices[i];
 
            if (arg1 < 0) {
                /* XXX W2DO? fails as "execution of script failed" */
                rc = RPMRC_FAIL;
            } else {
                arg1 += psm->countCorrection;
-               int ix = triggerIndices[i];
+
                if (triggersAlreadyRun == NULL || triggersAlreadyRun[ix] == 0) {
-                   ARGV_t argv = argvNew();
-                   argvAdd(&argv, *(triggerProgs + ix));
-                   rc = runScript(psm, pfx.data, triggertag(psm->sense), 
-                               &argv, triggerScripts[ix], arg1, arg2);
+                   /* construct script manually, this must not be freed */
+                   char *args[2] = { triggerProgs[ix], NULL };
+                   struct rpmScript_s script = {
+                       .tag = triggertag(psm->sense),
+                       .body = triggerScripts[ix],
+                       .args = args
+                   };
+                       
+                   rc = runScript(psm, pfx.data, &script, arg1, arg2);
                    if (triggersAlreadyRun != NULL)
                        triggersAlreadyRun[ix] = 1;
-                   argvFree(argv);
                }
            }
        }
@@ -1179,13 +830,7 @@ rpmRC rpmpsmStage(rpmpsm psm, pkgStage stage)
 
            if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOPRE)) {
                rc = rpmpsmNext(psm, PSM_SCRIPT);
-               if (rc != RPMRC_OK) {
-                   rpmlog(RPMLOG_ERR,
-                       _("%s: %s scriptlet failed (%d), skipping %s\n"),
-                       psm->stepName, tag2sln(psm->scriptTag), rc,
-                       rpmteNEVR(psm->te));
-                   break;
-               }
+               if (rc) break;
            }
        }
 
index 7d2a309..9a61ecf 100644 (file)
--- a/lib/psm.h
+++ b/lib/psm.h
@@ -6,8 +6,6 @@
  * Package state machine to handle a package from a transaction set.
  */
 
-#define _RPMSQ_INTERNAL 
-#include <rpm/rpmsq.h>
 #include <rpm/rpmcallback.h>
 
 extern int _psm_debug;
diff --git a/lib/rpmscript.c b/lib/rpmscript.c
new file mode 100644 (file)
index 0000000..4cde9bb
--- /dev/null
@@ -0,0 +1,399 @@
+#include "system.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#define _RPMSQ_INTERNAL
+#include <rpm/rpmsq.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmio.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmsw.h>
+#include <rpm/header.h>
+
+#include "rpmio/rpmlua.h"
+#include "lib/rpmscript.h"
+
+#include "debug.h"
+
+/**
+ * Run internal Lua script.
+ */
+static rpmRC runLuaScript(rpmts ts, ARGV_const_t prefixes,
+                  const char *sname, rpmlogLvl lvl,
+                  ARGV_t * argvp, const char *script, int arg1, int arg2)
+{
+    rpmRC rc = RPMRC_FAIL;
+#ifdef WITH_LUA
+    int rootFd = -1;
+    int xx;
+    ARGV_t argv = argvp ? *argvp : NULL;
+    rpmlua lua = NULL; /* Global state. */
+    rpmluav var;
+
+    rpmlog(RPMLOG_DEBUG, "%s: running <lua> scriptlet.\n", sname);
+    if (!rpmtsChrootDone(ts)) {
+       const char *rootDir = rpmtsRootDir(ts);
+       xx = chdir("/");
+       rootFd = open(".", O_RDONLY, 0);
+       if (rootFd >= 0) {
+           if (rootDir != NULL && !rstreq(rootDir, "/") && *rootDir == '/')
+               xx = chroot(rootDir);
+           xx = rpmtsSetChrootDone(ts, 1);
+       }
+    }
+
+    /* Create arg variable */
+    rpmluaPushTable(lua, "arg");
+    var = rpmluavNew();
+    rpmluavSetListMode(var, 1);
+    if (argv) {
+       char **p;
+       for (p = argv; *p; p++) {
+           rpmluavSetValue(var, RPMLUAV_STRING, *p);
+           rpmluaSetVar(lua, var);
+       }
+    }
+    if (arg1 >= 0) {
+       rpmluavSetValueNum(var, arg1);
+       rpmluaSetVar(lua, var);
+    }
+    if (arg2 >= 0) {
+       rpmluavSetValueNum(var, arg2);
+       rpmluaSetVar(lua, var);
+    }
+    var = rpmluavFree(var);
+    rpmluaPop(lua);
+
+    if (rpmluaRunScript(lua, script, sname) == 0) {
+       rc = RPMRC_OK;
+    }
+
+    rpmluaDelVar(lua, "arg");
+
+    if (rootFd >= 0) {
+       const char *rootDir = rpmtsRootDir(ts);
+       xx = fchdir(rootFd);
+       xx = close(rootFd);
+       if (rootDir != NULL && !rstreq(rootDir, "/") && *rootDir == '/')
+           xx = chroot(".");
+       xx = rpmtsSetChrootDone(ts, 0);
+    }
+#else
+    rpmlog(lvl, _("<lua> scriptlet support not built in\n"));
+#endif
+
+    return rc;
+}
+
+static const char * const SCRIPT_PATH = "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin";
+
+static void doScriptExec(rpmts ts, ARGV_const_t argv, ARGV_const_t prefixes,
+                       FD_t scriptFd, FD_t out)
+{
+    const char * rootDir;
+    int pipes[2];
+    int flag;
+    int fdno;
+    int xx;
+    int open_max;
+
+    (void) signal(SIGPIPE, SIG_DFL);
+    pipes[0] = pipes[1] = 0;
+    /* make stdin inaccessible */
+    xx = pipe(pipes);
+    xx = close(pipes[1]);
+    xx = dup2(pipes[0], STDIN_FILENO);
+    xx = close(pipes[0]);
+
+    /* XXX Force FD_CLOEXEC on all inherited fdno's. */
+    open_max = sysconf(_SC_OPEN_MAX);
+    if (open_max == -1) {
+       open_max = 1024;
+    }
+    for (fdno = 3; fdno < open_max; fdno++) {
+       flag = fcntl(fdno, F_GETFD);
+       if (flag == -1 || (flag & FD_CLOEXEC))
+           continue;
+       xx = fcntl(fdno, F_SETFD, FD_CLOEXEC);
+       /* XXX W2DO? debug msg for inheirited fdno w/o FD_CLOEXEC */
+    }
+
+    if (scriptFd != NULL) {
+       int sfdno = Fileno(scriptFd);
+       int ofdno = Fileno(out);
+       if (sfdno != STDERR_FILENO)
+           xx = dup2(sfdno, STDERR_FILENO);
+       if (ofdno != STDOUT_FILENO)
+           xx = dup2(ofdno, STDOUT_FILENO);
+       /* make sure we don't close stdin/stderr/stdout by mistake! */
+       if (ofdno > STDERR_FILENO && ofdno != sfdno)
+           xx = Fclose (out);
+       if (sfdno > STDERR_FILENO && ofdno != sfdno)
+           xx = Fclose (scriptFd);
+    }
+
+    {   char *ipath = rpmExpand("%{_install_script_path}", NULL);
+       const char *path = SCRIPT_PATH;
+
+       if (ipath && ipath[5] != '%')
+           path = ipath;
+
+       xx = setenv("PATH", path, 1);
+       ipath = _free(ipath);
+    }
+
+    for (ARGV_const_t pf = prefixes; pf && *pf; pf++) {
+       char *name = NULL;
+       int num = (pf - prefixes);
+
+       rasprintf(&name, "RPM_INSTALL_PREFIX%d", num);
+       setenv(name, *pf, 1);
+       free(name);
+
+       /* scripts might still be using the old style prefix */
+       if (num == 0) {
+           setenv("RPM_INSTALL_PREFIX", *pf, 1);
+       }
+    }
+       
+    rootDir = rpmtsRootDir(ts);
+    if (rootDir  != NULL) {    /* XXX can't happen */
+       if (!rpmtsChrootDone(ts) &&
+           !(rootDir[0] == '/' && rootDir[1] == '\0'))
+       {
+           xx = chroot(rootDir);
+       }
+       xx = chdir("/");
+
+       /* XXX Don't mtrace into children. */
+       unsetenv("MALLOC_CHECK_");
+
+       /* Permit libselinux to do the scriptlet exec. */
+       if (rpmtsSELinuxEnabled(ts) == 1) {     
+           xx = rpm_execcon(0, argv[0], argv, environ);
+       }
+
+       if (xx == 0) {
+           xx = execv(argv[0], argv);
+       }
+    }
+    _exit(127); /* exit 127 for compatibility with bash(1) */
+}
+
+/**
+ * Run an external script.
+ */
+static rpmRC runExtScript(rpmts ts, ARGV_const_t prefixes,
+                  const char *sname, rpmlogLvl lvl,
+                  ARGV_t * argvp, const char *script, int arg1, int arg2)
+{
+    FD_t scriptFd;
+    FD_t out = NULL;
+    char * fn = NULL;
+    int xx;
+    rpmRC rc = RPMRC_FAIL;
+    struct rpmsqElem sq;
+
+    memset(&sq, 0, sizeof(sq));
+    sq.reaper = 1;
+
+    rpmlog(RPMLOG_DEBUG, "%s: scriptlet start\n", sname);
+
+    if (script) {
+       const char * rootDir = rpmtsRootDir(ts);
+       FD_t fd;
+
+       fd = rpmMkTempFile((!rpmtsChrootDone(ts) ? rootDir : "/"), &fn);
+       if (fd == NULL || Ferror(fd)) {
+           rpmlog(RPMLOG_ERR, _("Couldn't create temporary file for %s: %s\n"),
+                  sname, strerror(errno));
+           goto exit;
+       }
+
+       if (rpmIsDebug() &&
+           (rstreq(*argvp[0], "/bin/sh") || rstreq(*argvp[0], "/bin/bash")))
+       {
+           static const char set_x[] = "set -x\n";
+           xx = Fwrite(set_x, sizeof(set_x[0]), sizeof(set_x)-1, fd);
+       }
+
+       xx = Fwrite(script, sizeof(script[0]), strlen(script), fd);
+       xx = Fclose(fd);
+
+       {   const char * sn = fn;
+           if (!rpmtsChrootDone(ts) && rootDir != NULL &&
+               !(rootDir[0] == '/' && rootDir[1] == '\0'))
+           {
+               sn += strlen(rootDir)-1;
+           }
+           argvAdd(argvp, sn);
+       }
+
+       if (arg1 >= 0) {
+           argvAddNum(argvp, arg1);
+       }
+       if (arg2 >= 0) {
+           argvAddNum(argvp, arg2);
+       }
+    }
+
+    scriptFd = rpmtsScriptFd(ts);
+    if (scriptFd != NULL) {
+       if (rpmIsVerbose()) {
+           out = fdDup(Fileno(scriptFd));
+       } else {
+           out = Fopen("/dev/null", "w.fdio");
+           if (Ferror(out)) {
+               out = fdDup(Fileno(scriptFd));
+           }
+       }
+    } else {
+       out = fdDup(STDOUT_FILENO);
+    }
+    if (out == NULL) { 
+       rpmlog(RPMLOG_ERR, _("Couldn't duplicate file descriptor: %s: %s\n"),
+              sname, strerror(errno));
+       goto exit;
+    }
+
+    xx = rpmsqFork(&sq);
+    if (sq.child == 0) {
+       rpmlog(RPMLOG_DEBUG, "%s: execv(%s) pid %d\n",
+              sname, *argvp[0], (unsigned)getpid());
+       doScriptExec(ts, *argvp, prefixes, scriptFd, out);
+    }
+
+    if (sq.child == (pid_t)-1) {
+       rpmlog(RPMLOG_ERR, _("Couldn't fork %s: %s\n"), sname, strerror(errno));
+       goto exit;
+    }
+
+    rpmsqWait(&sq);
+
+    rpmlog(RPMLOG_DEBUG, "%s: waitpid(%d) rc %d status %x\n",
+          sname, (unsigned)sq.child, (unsigned)sq.reaped, sq.status);
+
+    if (sq.reaped < 0) {
+       rpmlog(lvl, _("%s scriptlet failed, waitpid(%d) rc %d: %s\n"),
+                sname, sq.child, sq.reaped, strerror(errno));
+    } else if (!WIFEXITED(sq.status) || WEXITSTATUS(sq.status)) {
+       if (WIFSIGNALED(sq.status)) {
+           rpmlog(lvl, _("%s scriptlet failed, signal %d\n"),
+                   sname, WTERMSIG(sq.status));
+       } else {
+           rpmlog(lvl, _("%s scriptlet failed, exit status %d\n"),
+                  sname, WEXITSTATUS(sq.status));
+       }
+    } else {
+       /* if we get this far we're clear */
+       rc = RPMRC_OK;
+    }
+
+exit:
+    if (out)
+       xx = Fclose(out);       /* XXX dup'd STDOUT_FILENO */
+
+    if (script) {
+       if (!rpmIsDebug())
+           xx = unlink(fn);
+       fn = _free(fn);
+    }
+    return rc;
+}
+
+rpmRC rpmScriptRun(rpmScript script, int arg1, int arg2,
+                  rpmts ts, ARGV_const_t prefixes, int warn_only)
+{
+    ARGV_t args = NULL;
+    rpmlogLvl lvl = warn_only ? RPMLOG_WARNING : RPMLOG_ERR;
+    rpmRC rc;
+
+    if (script == NULL) return RPMRC_OK;
+
+    /* construct a new argv as we can't modify the one from header */
+    if (script->args) {
+       argvAppend(&args, script->args);
+    } else {
+       argvAdd(&args, "/bin/sh");
+    }
+
+    rpmswEnter(rpmtsOp(ts, RPMTS_OP_SCRIPTLETS), 0);
+    if (rstreq(args[0], "<lua>")) {
+       rc = runLuaScript(ts, prefixes, script->descr, lvl, &args, script->body, arg1, arg2);
+    } else {
+       rc = runExtScript(ts, prefixes, script->descr, lvl, &args, script->body, arg1, arg2);
+    }
+    rpmswExit(rpmtsOp(ts, RPMTS_OP_SCRIPTLETS), 0);
+    argvFree(args);
+
+    return rc;
+}
+
+static rpmTag getProgTag(rpmTag scriptTag)
+{
+    switch (scriptTag) {
+    case RPMTAG_PREIN:         return RPMTAG_PREINPROG;
+    case RPMTAG_POSTIN:        return RPMTAG_POSTINPROG;
+    case RPMTAG_PREUN:                 return RPMTAG_PREUNPROG;
+    case RPMTAG_POSTUN:        return RPMTAG_POSTUNPROG;
+    case RPMTAG_PRETRANS:      return RPMTAG_PRETRANSPROG;
+    case RPMTAG_POSTTRANS:     return RPMTAG_POSTTRANSPROG;
+    case RPMTAG_VERIFYSCRIPT:  return RPMTAG_VERIFYSCRIPTPROG;
+    default:                   return RPMTAG_NOT_FOUND;
+    }
+}
+
+static const char * tag2sln(rpmTag tag)
+{
+    switch ((rpm_tag_t) tag) {
+    case RPMTAG_PRETRANS:       return "%pretrans";
+    case RPMTAG_TRIGGERPREIN:   return "%triggerprein";
+    case RPMTAG_PREIN:          return "%pre";
+    case RPMTAG_POSTIN:         return "%post";
+    case RPMTAG_TRIGGERIN:      return "%triggerin";
+    case RPMTAG_TRIGGERUN:      return "%triggerun";
+    case RPMTAG_PREUN:          return "%preun";
+    case RPMTAG_POSTUN:         return "%postun";
+    case RPMTAG_POSTTRANS:      return "%posttrans";
+    case RPMTAG_TRIGGERPOSTUN:  return "%triggerpostun";
+    case RPMTAG_VERIFYSCRIPT:   return "%verify";
+    default: break;
+    }
+    return "%unknownscript";
+}
+
+rpmScript rpmScriptFromTag(Header h, rpmTag scriptTag)
+{
+    rpmScript script = NULL;
+    rpmTag progTag = getProgTag(scriptTag);
+
+    if (headerIsEntry(h, scriptTag) || headerIsEntry(h, progTag)) {
+       struct rpmtd_s prog;
+       char *nevra = headerGetAsString(h, RPMTAG_NEVRA);
+
+       script = xcalloc(1, sizeof(*script));
+       script->tag = scriptTag;
+       script->body = headerGetAsString(h, scriptTag);
+       rasprintf(&script->descr, "%s(%s)", tag2sln(scriptTag), nevra);
+
+       if (headerGet(h, progTag, &prog, (HEADERGET_ALLOC|HEADERGET_ARGV))) {
+           script->args = prog.data;
+       }
+       free(nevra);
+    }
+    return script;
+}
+
+rpmScript rpmScriptFree(rpmScript script)
+{
+    if (script) {
+       free(script->args);
+       free(script->body);
+       free(script->descr);
+       free(script);
+    }
+    return NULL;
+}
diff --git a/lib/rpmscript.h b/lib/rpmscript.h
new file mode 100644 (file)
index 0000000..8df2cc3
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef _RPMSCRIPT_H
+#define _RPMSCRIPT_H
+
+#include <rpm/rpmtypes.h>
+#include <rpm/argv.h>
+
+typedef struct rpmScript_s * rpmScript;
+
+struct rpmScript_s {
+    rpmTag tag;                /* script tag */
+    char **args;       /* scriptlet call arguments */
+    char *body;                /* script body */
+    char *descr;       /* description for logging */
+};
+
+RPM_GNUC_INTERNAL
+rpmScript rpmScriptFromTag(Header h, rpmTag scriptTag);
+
+RPM_GNUC_INTERNAL
+rpmScript rpmScriptFree(rpmScript script);
+
+RPM_GNUC_INTERNAL
+rpmRC rpmScriptRun(rpmScript script, int arg1, int arg2,
+                   rpmts ts, ARGV_const_t prefixes, int warn_only);
+
+#endif /* _RPMSCRIPT_H */