Imported Upstream version 3.3.1
[debian/amanda] / common-src / util.c
index 1c1856a94119e3bddd34cc8fbdbc83454e5c7070..b9172ca41950fa716235833f7e2b0c2853fbd957 100644 (file)
 
 #include "amanda.h"
 #include "util.h"
+#include "match.h"
 #include <regex.h>
 #include "arglist.h"
 #include "clock.h"
 #include "sockaddr-util.h"
 #include "conffile.h"
 #include "base64.h"
+#include "stream.h"
+#include "pipespawn.h"
+#include <glib.h>
+#include <string.h>
 
 static int make_socket(sa_family_t family);
 static int connect_port(sockaddr_union *addrp, in_port_t port, char *proto,
@@ -91,6 +96,11 @@ make_socket(
     return s;
 }
 
+GQuark am_util_error_quark(void)
+{
+    return g_quark_from_static_string("am-util-error-quark");
+}
+
 /* addrp is my address */
 /* svaddr is the address of the remote machine */
 /* return socket on success */
@@ -283,35 +293,85 @@ bind_portrange(
            socklen = SS_LEN(addrp);
            if (bind(s, (struct sockaddr *)addrp, socklen) >= 0) {
                if (servPort == NULL) {
-                   dbprintf(_("bind_portrange2: Try  port %d: Available - Success\n"), port);
+                   g_debug(_("bind_portrange2: Try  port %d: Available - Success"), port);
                } else {
-                   dbprintf(_("bind_portrange2: Try  port %d: Owned by %s - Success.\n"), port, servPort->s_name);
+                   g_debug(_("bind_portrange2: Try  port %d: Owned by %s - Success."), port, servPort->s_name);
                }
                return 0;
            }
            if (errno != EAGAIN && errno != EBUSY)
                save_errno = errno;
            if (servPort == NULL) {
-               dbprintf(_("bind_portrange2: Try  port %d: Available - %s\n"),
+               g_debug(_("bind_portrange2: Try  port %d: Available - %s"),
                        port, strerror(errno));
            } else {
-               dbprintf(_("bind_portrange2: Try  port %d: Owned by %s - %s\n"),
+               g_debug(_("bind_portrange2: Try  port %d: Owned by %s - %s"),
                        port, servPort->s_name, strerror(errno));
            }
        } else {
-               dbprintf(_("bind_portrange2: Skip port %d: Owned by %s.\n"),
+               g_debug(_("bind_portrange2: Skip port %d: Owned by %s."),
                      port, servPort->s_name);
        }
        if (++port > last_port)
            port = first_port;
     }
-    dbprintf(_("bind_portrange: all ports between %d and %d busy\n"),
+    g_debug(_("bind_portrange: all ports between %d and %d busy"),
                  first_port,
                  last_port);
     errno = save_errno;
     return -1;
 }
 
+int
+interruptible_accept(
+    int sock,
+    struct sockaddr *addr,
+    socklen_t *addrlen,
+    gboolean (*prolong)(gpointer data),
+    gpointer prolong_data)
+{
+    SELECT_ARG_TYPE readset;
+    struct timeval tv;
+    int nfound;
+
+    if (sock < 0 || sock >= FD_SETSIZE) {
+       g_debug("interruptible_accept: bad socket %d", sock);
+       return EBADF;
+    }
+
+    memset(&readset, 0, SIZEOF(readset));
+
+    while (1) {
+       if (!prolong(prolong_data)) {
+           errno = 0;
+           return -1;
+       }
+
+       FD_ZERO(&readset);
+       FD_SET(sock, &readset);
+
+       /* try accepting for 1s */
+       memset(&tv, 0, SIZEOF(tv));
+       tv.tv_sec = 1;
+
+       nfound = select(sock+1, &readset, NULL, NULL, &tv);
+       if (nfound < 0) {
+           return -1;
+       } else if (nfound == 0) {
+           continue;
+       } else if (!FD_ISSET(sock, &readset)) {
+           g_debug("interruptible_accept: select malfunction");
+           errno = EBADF;
+           return -1;
+       } else {
+           int rv = accept(sock, addr, addrlen);
+           if (rv < 0 && errno == EAGAIN)
+               continue;
+           return rv;
+       }
+    }
+}
+
 /*
  * Writes out the entire iovec
  */
@@ -363,74 +423,144 @@ full_writev(
 }
 
 
-int
-needs_quotes(
-    const char * str)
-{
-    return (match("[ \t\f\r\n\"]", str) != 0);
-}
-
-
 /*
- * For backward compatibility we are trying for minimal quoting.
- * We only quote a string if it contains whitespace or is misquoted...
+ * For backward compatibility we are trying for minimal quoting.  Unless ALWAYS
+ * is true, we only quote a string if it contains whitespace or is misquoted...
  */
 
 char *
-quote_string(
-    const char *str)
+quote_string_maybe(
+    const char *str,
+    gboolean always)
 {
     char *  s;
     char *  ret;
 
     if ((str == NULL) || (*str == '\0')) {
        ret = stralloc("\"\"");
-    } else if ((match("[:\'\\\"[:space:][:cntrl:]]", str)) == 0) {
-       /*
-        * String does not need to be quoted since it contains
-        * neither whitespace, control or quote characters.
-        */
-       ret = stralloc(str);
     } else {
-       /*
-        * Allocate maximum possible string length.
-        * (a string of all quotes plus room for leading ", trailing " and NULL)
-        */
-       ret = s = alloc((strlen(str) * 2) + 2 + 1);
-       *(s++) = '"';
-       while (*str != '\0') {
-            if (*str == '\t') {
-                *(s++) = '\\';
-                *(s++) = 't';
-               str++;
-               continue;
-           } else if (*str == '\n') {
-                *(s++) = '\\';
-                *(s++) = 'n';
-               str++;
-               continue;
-           } else if (*str == '\r') {
-                *(s++) = '\\';
-                *(s++) = 'r';
-               str++;
-               continue;
-           } else if (*str == '\f') {
-                *(s++) = '\\';
-                *(s++) = 'f';
-               str++;
-               continue;
-           } else if (*str == '\\') {
-                *(s++) = '\\';
-                *(s++) = '\\';
-               str++;
-               continue;
-           }
-            if (*str == '"')
-                *(s++) = '\\';
-            *(s++) = *(str++);
+       const char *r;
+       for (r = str; *r; r++) {
+           if (*r == ':' || *r == '\'' || *r == '\\' || *r == '\"' ||
+               *r <= ' ' || *r == 0x7F )
+               always = 1;
+       }
+       if (!always) {
+           /*
+            * String does not need to be quoted since it contains
+            * neither whitespace, control or quote characters.
+            */
+           ret = stralloc(str);
+       } else {
+           /*
+            * Allocate maximum possible string length.
+            * (a string of all quotes plus room for leading ", trailing " and
+            *  NULL)
+            */
+           ret = s = alloc((strlen(str) * 2) + 2 + 1);
+           *(s++) = '"';
+           while (*str != '\0') {
+                if (*str == '\t') {
+                    *(s++) = '\\';
+                    *(s++) = 't';
+                   str++;
+                   continue;
+               } else if (*str == '\n') {
+                    *(s++) = '\\';
+                    *(s++) = 'n';
+                   str++;
+                   continue;
+               } else if (*str == '\r') {
+                    *(s++) = '\\';
+                    *(s++) = 'r';
+                   str++;
+                   continue;
+               } else if (*str == '\f') {
+                    *(s++) = '\\';
+                    *(s++) = 'f';
+                   str++;
+                   continue;
+               } else if (*str == '\\') {
+                    *(s++) = '\\';
+                    *(s++) = '\\';
+                   str++;
+                   continue;
+               }
+                if (*str == '"')
+                    *(s++) = '\\';
+                *(s++) = *(str++);
+            }
+            *(s++) = '"';
+            *s = '\0';
         }
-        *(s++) = '"';
-        *s = '\0';
+    }
+    return (ret);
+}
+
+
+int
+len_quote_string_maybe(
+    const char *str,
+    gboolean always)
+{
+    int   ret;
+
+    if ((str == NULL) || (*str == '\0')) {
+       ret = 0;
+    } else {
+       const char *r;
+       for (r = str; *r; r++) {
+           if (*r == ':' || *r == '\'' || *r == '\\' || *r == '\"' ||
+               *r <= ' ' || *r == 0x7F )
+               always = 1;
+       }
+       if (!always) {
+           /*
+            * String does not need to be quoted since it contains
+            * neither whitespace, control or quote characters.
+            */
+           ret = strlen(str);
+       } else {
+           /*
+            * Allocate maximum possible string length.
+            * (a string of all quotes plus room for leading ", trailing " and
+            *  NULL)
+            */
+           ret = 1;
+               while (*str != '\0') {
+                if (*str == '\t') {
+                    ret++;
+                    ret++;
+                   str++;
+                   continue;
+               } else if (*str == '\n') {
+                    ret++;
+                    ret++;
+                   str++;
+                   continue;
+               } else if (*str == '\r') {
+                    ret++;
+                    ret++;
+                   str++;
+                   continue;
+               } else if (*str == '\f') {
+                    ret++;
+                    ret++;
+                   str++;
+                   continue;
+               } else if (*str == '\\') {
+                    ret++;
+                    ret++;
+                   str++;
+                   continue;
+               }
+                if (*str == '"')
+                   ret++;
+               ret++;
+                str++;
+            }
+           ret++;
+       }
     }
     return (ret);
 }
@@ -499,13 +629,19 @@ gchar **
 split_quoted_strings(
     const gchar *string)
 {
-    char *local = g_strdup(string);
-    char *start = local;
-    char *p = local;
+    char *local;
+    char *start;
+    char *p;
     char **result;
-    GPtrArray *strs = g_ptr_array_new();
+    GPtrArray *strs;
     int iq = 0;
 
+    if (!string)
+       return NULL;
+
+    p = start = local = g_strdup(string);
+    strs = g_ptr_array_new();
+
     while (*p) {
        if (!iq && *p == ' ') {
            *p = '\0';
@@ -530,7 +666,7 @@ split_quoted_strings(
     result = g_new0(char *, strs->len + 1);
     memmove(result, strs->pdata, sizeof(char *) * strs->len);
 
-    g_ptr_array_free(strs, FALSE); /* FALSE => don't free strings */
+    g_ptr_array_free(strs, TRUE); /* TRUE => free pdata, strings are not freed */
     g_free(local);
 
     return result;
@@ -593,6 +729,309 @@ sanitize_string(
     return (ret);
 }
 
+char *hexencode_string(const char *str)
+{
+    size_t orig_len, new_len, i;
+    GString *s;
+    gchar *ret;
+    if (!str) {
+        s = g_string_sized_new(0);
+        goto cleanup;
+    }
+    new_len = orig_len = strlen(str);
+    for (i = 0; i < orig_len; i++) {
+        if (!g_ascii_isalnum(str[i])) {
+            new_len += 2;
+        }
+    }
+    s = g_string_sized_new(new_len);
+
+    for (i = 0; i < orig_len; i++) {
+        if (g_ascii_isalnum(str[i])) {
+            g_string_append_c(s, str[i]);
+        } else {
+            g_string_append_printf(s, "%%%02hhx", str[i]);
+        }
+    }
+
+cleanup:
+    ret = s->str;
+    g_string_free(s, FALSE);
+    return ret;
+}
+
+char *hexdecode_string(const char *str, GError **err)
+{
+    size_t orig_len, new_len, i;
+    GString *s;
+    gchar *ret;
+    if (!str) {
+        s = g_string_sized_new(0);
+        goto cleanup;
+    }
+    new_len = orig_len = strlen(str);
+    for (i = 0; i < orig_len; i++) {
+        if (str[i] == '%') {
+            new_len -= 2;
+        }
+    }
+    s = g_string_sized_new(new_len);
+
+    for (i = 0; (orig_len > 2) && (i < orig_len-2); i++) {
+        if (str[i] == '%') {
+            gchar tmp = 0;
+            size_t j;
+            for (j = 1; j < 3; j++) {
+                tmp <<= 4;
+                if (str[i+j] >= '0' && str[i+j] <= '9') {
+                    tmp += str[i+j] - '0';
+                } else if (str[i+j] >= 'a' && str[i+j] <= 'f') {
+                    tmp += str[i+j] - 'a' + 10;
+                } else if (str[i+j] >= 'A' && str[i+j] <= 'F') {
+                    tmp += str[i+j] - 'A' + 10;
+                } else {
+                    /* error */
+                    g_set_error(err, am_util_error_quark(), AM_UTIL_ERROR_HEXDECODEINVAL,
+                        "Illegal character (non-hex) 0x%02hhx at offset %zd", str[i+j], i+j);
+                    g_string_truncate(s, 0);
+                    goto cleanup;
+                }
+            }
+            if (!tmp) {
+                g_set_error(err, am_util_error_quark(), AM_UTIL_ERROR_HEXDECODEINVAL,
+                    "Encoded NULL at starting offset %zd", i);
+                g_string_truncate(s, 0);
+                goto cleanup;
+            }
+            g_string_append_c(s, tmp);
+            i += 2;
+        } else {
+            g_string_append_c(s, str[i]);
+        }
+    }
+    for ( /*nothing*/; i < orig_len; i++) {
+        if (str[i] == '%') {
+            g_set_error(err, am_util_error_quark(), AM_UTIL_ERROR_HEXDECODEINVAL,
+                "'%%' found at offset %zd, but fewer than two characters follow it (%zd)", i, orig_len-i-1);
+            g_string_truncate(s, 0);
+            goto cleanup;
+        } else {
+            g_string_append_c(s, str[i]);
+        }
+    }
+
+cleanup:
+    ret = s->str;
+    g_string_free(s, FALSE);
+    return ret;
+}
+
+/* Helper for parse_braced_component; this will turn a single element array
+ * matching /^\d+\.\.\d+$/ into a sequence of numbered array elements. */
+static GPtrArray *
+expand_braced_sequence(GPtrArray *arr)
+{
+    char *elt, *p;
+    char *l, *r;
+    int ldigits, rdigits, ndigits;
+    guint64 start, end;
+    gboolean leading_zero;
+
+    /* check whether the element matches the pattern */
+    /* expand last element of the array only */
+    elt = g_ptr_array_index(arr, arr->len-1);
+    ldigits = 0;
+    for (l = p = elt; *p && g_ascii_isdigit(*p); p++)
+       ldigits++;
+    if (ldigits == 0)
+       return arr;
+    if (*(p++) != '.')
+       return arr;
+    if (*(p++) != '.')
+       return arr;
+    rdigits = 0;
+    for (r = p; *p && g_ascii_isdigit(*p); p++)
+       rdigits++;
+    if (rdigits == 0)
+       return arr;
+    if (*p)
+       return arr;
+
+    /* we have a match, so extract start and end */
+    start = g_ascii_strtoull(l, NULL, 10);
+    end = g_ascii_strtoull(r, NULL, 10);
+    leading_zero = *l == '0';
+    ndigits = MAX(ldigits, rdigits);
+    if (start > end)
+       return arr;
+
+    /* sanity check.. */
+    if (end - start > 100000)
+       return arr;
+
+    /* remove last from the array */
+    g_ptr_array_remove_index(arr, arr->len - 1);
+
+    /* Add new elements */
+    while (start <= end) {
+       if (leading_zero) {
+           g_ptr_array_add(arr, g_strdup_printf("%0*ju",
+                       ndigits, (uintmax_t)start));
+       } else {
+           g_ptr_array_add(arr, g_strdup_printf("%ju", (uintmax_t)start));
+       }
+       start++;
+    }
+
+    return arr;
+}
+
+/* Helper for expand_braced_alternates; returns a list of un-escaped strings
+ * for the first "component" of str, where a component is a plain string or a
+ * brace-enclosed set of alternatives.  str is pointing to the first character
+ * of the next component on return. */
+static GPtrArray *
+parse_braced_component(char **str)
+{
+    GPtrArray *result = g_ptr_array_new();
+
+    if (**str == '{') {
+       char *p = (*str)+1;
+       char *local = g_malloc(strlen(*str)+1);
+       char *current = local;
+       char *c = current;
+
+       while (1) {
+           if (*p == '\0' || *p == '{') {
+               /* unterminated { .. } or extra '{' */
+               amfree(local);
+               g_ptr_array_free(result, TRUE);
+               return NULL;
+           }
+
+           if (*p == '}' || *p == ',') {
+               *c = '\0';
+               g_ptr_array_add(result, g_strdup(current));
+               result = expand_braced_sequence(result);
+               current = ++c;
+
+               if (*p == '}')
+                   break;
+               else
+                   p++;
+           }
+
+           if (*p == '\\') {
+               if (*(p+1) == '{' || *(p+1) == '}' || *(p+1) == '\\' || *(p+1) == ',')
+                   p++;
+           }
+           *(c++) = *(p++);
+       }
+
+       amfree(local);
+
+       if (*p)
+           *str = p+1;
+       else
+           *str = p;
+    } else {
+       /* no braces -- just un-escape a plain string */
+       char *local = g_malloc(strlen(*str)+1);
+       char *r = local;
+       char *p = *str;
+
+       while (*p && *p != '{') {
+           if (*p == '\\') {
+               if (*(p+1) == '{' || *(p+1) == '}' || *(p+1) == '\\' || *(p+1) == ',')
+                   p++;
+           }
+           *(r++) = *(p++);
+       }
+       *r = '\0';
+       g_ptr_array_add(result, local);
+       *str = p;
+    }
+
+    return result;
+}
+
+GPtrArray *
+expand_braced_alternates(
+    char * source)
+{
+    GPtrArray *rval = g_ptr_array_new();
+
+    g_ptr_array_add(rval, g_strdup(""));
+
+    while (*source) {
+       GPtrArray *new_components;
+       GPtrArray *new_rval;
+       guint i, j;
+
+       new_components = parse_braced_component(&source);
+       if (!new_components) {
+           /* parse error */
+           g_ptr_array_free(rval, TRUE);
+           return NULL;
+       }
+
+       new_rval = g_ptr_array_new();
+
+       /* do a cartesian join of rval and new_components */
+       for (i = 0; i < rval->len; i++) {
+           for (j = 0; j < new_components->len; j++) {
+               g_ptr_array_add(new_rval, g_strconcat(
+                   g_ptr_array_index(rval, i),
+                   g_ptr_array_index(new_components, j),
+                   NULL));
+           }
+       }
+
+       g_ptr_array_free(rval, TRUE);
+       g_ptr_array_free(new_components, TRUE);
+       rval = new_rval;
+    }
+
+    return rval;
+}
+
+char *
+collapse_braced_alternates(
+    GPtrArray *source)
+{
+    GString *result = NULL;
+    guint i;
+
+    result = g_string_new("{");
+
+    for (i = 0; i < source->len; i ++) {
+       const char *str = g_ptr_array_index(source, i);
+       char *qstr = NULL;
+
+       if (strchr(str, ',') || strchr(str, '\\') ||
+           strchr(str, '{') || strchr(str, '}')) {
+           const char *s;
+           char *d;
+
+           s = str;
+           qstr = d = g_malloc(strlen(str)*2+1);
+           while (*s) {
+               if (*s == ',' || *s == '\\' || *s == '{' || *s == '}')
+                   *(d++) = '\\';
+               *(d++) = *(s++);
+           }
+           *(d++) = '\0';
+       }
+       g_string_append_printf(result, "%s%s", qstr? qstr : str,
+               (i < source->len-1)? "," : "");
+       if (qstr)
+           g_free(qstr);
+    }
+
+    g_string_append(result, "}");
+    return g_string_free(result, FALSE);
+}
+
 /*
    Return 0 if the following characters are present
    * ( ) < > [ ] , ; : ! $ \ / "
@@ -665,7 +1104,7 @@ int copy_file(
     return 0;
 }
 
-#ifndef HAVE_READLINE
+#ifndef HAVE_LIBREADLINE
 /*
  * simple readline() replacements, used when we don't have readline
  * support from the system.
@@ -910,6 +1349,7 @@ check_running_as(running_as_flags who)
        case RUNNING_AS_ANY:
            uid_target = uid_me;
            uname_target = uname_me;
+           amfree(uname_me);
            return;
 
        case RUNNING_AS_ROOT:
@@ -973,12 +1413,39 @@ int
 set_root_privs(int need_root)
 {
 #ifndef SINGLE_USERID
-    if (need_root) {
+    static gboolean first_call = TRUE;
+    static uid_t unpriv = 1;
+
+    if (first_call) {
+       /* save the original real userid (that of our invoker) */
+       unpriv = getuid();
+
+       /* and set all of our userids (including, importantly, the saved
+        * userid) to 0 */
+       setuid(0);
+
+       /* don't need to do this next time */
+       first_call = FALSE;
+    }
+
+    if (need_root == 1) {
+       if (geteuid() == 0) return 1; /* already done */
+
         if (seteuid(0) == -1) return 0;
         /* (we don't switch the group back) */
+    } else if (need_root == -1) {
+       /* make sure the euid is 0 so that we can set the uid */
+       if (geteuid() != 0) {
+           if (seteuid(0) == -1) return 0;
+       }
+
+       /* now set the uid to the unprivileged userid */
+       if (setuid(unpriv) == -1) return 0;
     } else {
-       if (geteuid() != 0) return 0;
-        if (seteuid(getuid()) == -1) return 0;
+       if (geteuid() != 0) return 1; /* already done */
+
+       /* set the *effective* userid only */
+        if (seteuid(unpriv) == -1) return 0;
         if (setegid(getgid()) == -1) return 0;
     }
 #else
@@ -991,15 +1458,15 @@ int
 become_root(void)
 {
 #ifndef SINGLE_USERID
-    // if euid !=0, it set only euid
-    if (setuid(0) == -1) return 0;
-    // will set ruid because euid == 0.
+    /* first, set the effective userid to 0 */
+    if (seteuid(0) == -1) return 0;
+
+    /* then, set all of the userids to 0 */
     if (setuid(0) == -1) return 0;
 #endif
     return 1;
 }
 
-
 char *
 base64_decode_alloc_string(
     char *in)
@@ -1019,30 +1486,22 @@ base64_decode_alloc_string(
 }
 
 
-/* A GHFunc (callback for g_hash_table_foreach) */
-void count_proplist(
-    gpointer key_p G_GNUC_UNUSED,
-    gpointer value_p,
-    gpointer user_data_p)
-{
-    property_t *value_s = value_p;
-    int    *nb = user_data_p;
-    GSList  *value;
-
-    for(value=value_s->values; value != NULL; value = value->next) {
-       (*nb)++;
-    }
-}
-
-/* A GHFunc (callback for g_hash_table_foreach) */
-void proplist_add_to_argv(
+/* A GHFunc (callback for g_hash_table_foreach),
+ * Store a property and it's value in an ARGV.
+ *
+ * @param key_p: (char *) property name.
+ * @param value_p: (GSList *) property values list.
+ * @param user_data_p: (char ***) pointer to ARGV.
+ */
+static void
+proplist_add_to_argv(
     gpointer key_p,
     gpointer value_p,
     gpointer user_data_p)
 {
     char         *property_s = key_p;
     property_t   *value_s = value_p;
-    char       ***argv = user_data_p;
+    GPtrArray    *argv_ptr = user_data_p;
     GSList       *value;
     char         *q, *w, *qprop;
 
@@ -1056,14 +1515,20 @@ void proplist_add_to_argv(
     qprop = stralloc2("--", q);
     amfree(q);
     for(value=value_s->values; value != NULL; value = value->next) {
-       **argv = stralloc(qprop);
-       (*argv)++;
-       **argv = stralloc((char *)value->data);
-       (*argv)++;
+       g_ptr_array_add(argv_ptr, stralloc(qprop));
+       g_ptr_array_add(argv_ptr, stralloc((char *)value->data));
     }
     amfree(qprop);
 }
 
+void
+property_add_to_argv(
+    GPtrArray  *argv_ptr,
+    GHashTable *proplist)
+{
+    g_hash_table_foreach(proplist, &proplist_add_to_argv, argv_ptr);
+}
+
 
 /*
  * Process parameters
@@ -1110,3 +1575,103 @@ get_pcontext(void)
 {
     return pcontext;
 }
+
+#ifdef __OpenBSD__
+void
+openbsd_fd_inform(void)
+{
+    int i;
+    for (i = DATA_FD_OFFSET; i < DATA_FD_OFFSET + DATA_FD_COUNT*2; i++) {
+       /* a simple fcntl() will cause the library to "look" at this file
+        * descriptor, which is good enough */
+       (void)fcntl(i, F_GETFL);
+    }
+}
+#endif
+
+void
+debug_executing(
+    GPtrArray *argv_ptr)
+{
+    guint i;
+    char *cmdline = stralloc((char *)g_ptr_array_index(argv_ptr, 0));
+
+    for (i = 1; i < argv_ptr->len-1; i++) {
+       char *arg = g_shell_quote((char *)g_ptr_array_index(argv_ptr, i));
+       cmdline = vstrextend(&cmdline, " ", arg, NULL);
+       amfree(arg);
+    }
+    g_debug("Executing: %s\n", cmdline);
+    amfree(cmdline);
+}
+
+char *
+get_first_line(
+    GPtrArray *argv_ptr)
+{
+    char *output_string = NULL;
+    int   inpipe[2], outpipe[2], errpipe[2];
+    int   pid;
+    FILE *out, *err;
+
+    assert(argv_ptr != NULL);
+    assert(argv_ptr->pdata != NULL);
+    assert(argv_ptr->len >= 1);
+
+    if (pipe(inpipe) == -1) {
+       error(_("error [open pipe: %s]"), strerror(errno));
+       /*NOTREACHED*/
+    }
+    if (pipe(outpipe) == -1) {
+       error(_("error [open pipe: %s]"), strerror(errno));
+       /*NOTREACHED*/
+    }
+    if (pipe(errpipe) == -1) {
+       error(_("error [open pipe: %s]"), strerror(errno));
+       /*NOTREACHED*/
+    }
+
+    fflush(stdout);
+    switch(pid = fork()) {
+    case -1:
+       error(_("error [fork: %s]"), strerror(errno));
+       /*NOTREACHED*/
+
+    default:   /* parent process */
+       aclose(inpipe[0]);
+       aclose(outpipe[1]);
+       aclose(errpipe[1]);
+       break;
+
+    case 0: /* child process */
+       aclose(inpipe[1]);
+       aclose(outpipe[0]);
+       aclose(errpipe[0]);
+
+       dup2(inpipe[0], 0);
+       dup2(outpipe[1], 1);
+       dup2(errpipe[1], 2);
+
+       debug_executing(argv_ptr);
+       g_fprintf(stdout, "unknown\n");
+       execv((char *)*argv_ptr->pdata, (char **)argv_ptr->pdata);
+       error(_("error [exec %s: %s]"), (char *)*argv_ptr->pdata, strerror(errno));
+    }
+
+    aclose(inpipe[1]);
+
+    out = fdopen(outpipe[0],"r");
+    err = fdopen(errpipe[0],"r");
+
+    output_string = agets(out);
+    if (!output_string)
+       output_string = agets(err);
+
+    fclose(out);
+    fclose(err);
+
+    waitpid(pid, NULL, 0);
+
+    return output_string;
+}
+