Welcome to little lamb

Code » anopa » master » tree

[master] / src / libanopa / exec_oneshot.c

/*
 * anopa - Copyright (C) 2015-2017 Olivier Brunel
 *
 * exec_oneshot.c
 * Copyright (C) 2015-2018 Olivier Brunel <jjk@jjacky.com>
 *
 * This file is part of anopa.
 *
 * anopa is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * anopa is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * anopa. If not, see http://www.gnu.org/licenses/
 */

#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <strings.h>
#include <errno.h>
#include <skalibs/allreadwrite.h>
#include <skalibs/djbunix.h>
#include <skalibs/sig.h>
#include <skalibs/selfpipe.h>
#include <skalibs/bytestr.h>
#include <skalibs/tai.h>
#include <skalibs/types.h>
#include <anopa/service.h>
#include <anopa/err.h>
#include <anopa/output.h>
#include <anopa/rc.h>
#include "service_internal.h"

int
_exec_oneshot (int si, aa_mode mode)
{
    aa_service *s = aa_service (si);
    int is_start = (mode & AA_MODE_START) ? 1 : 0;
    char * const filename = (is_start) ? AA_START_FILENAME : AA_STOP_FILENAME;
    size_t l_fn = (is_start) ? sizeof(AA_START_FILENAME) : sizeof(AA_STOP_FILENAME);
    size_t l_sn = strlen (aa_service_name (s));
    char buf[l_sn + 1 +l_fn];
    struct stat st;
    const char *_err = NULL; /* silence warning */
    int _errno = 0; /* silence warning */
    int p_int[2];
    int p_in[2];
    int p_out[2];
    int p_prg[2];
    pid_t pid;
    char c;

    byte_copy (buf, l_sn, aa_service_name (s));
    byte_copy (buf + l_sn, 1, "/");
    byte_copy (buf + l_sn + 1, l_fn, filename);

    if (stat (buf, &st) < 0)
    {
        tain_now_g ();

        if (errno != ENOENT)
        {
            _errno = errno;
            _err = "stat ";
            goto err;
        }

        /* nothing to do, just set it done */
        s->st.event = (is_start) ? AA_EVT_STARTED : AA_EVT_STOPPED;
        tain_copynow (&s->st.stamp);
        if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
            aa_strerr_warnu2sys ("write service status file for ", aa_service_name (s));

        if (_exec_cb)
            _exec_cb (si, s->st.event, 0);

        /* this was not a failure, but we return -1 to trigger a
         * aa_scan_mainlist() anyways, since the service changed state */
        return -1;
    }

    s->st.event = (is_start) ? AA_EVT_STARTING : AA_EVT_STOPPING;
    tain_copynow (&s->st.stamp);
    aa_service_status_set_msg (&s->st, "");
    if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
    {
        s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
        s->st.code = ERR_WRITE_STATUS;
        aa_service_status_set_msg (&s->st, strerror (errno));

        if (_exec_cb)
            _exec_cb (si, s->st.event, 0);
        return -1;
    }

    if (pipecoe (p_int) < 0)
    {
        _errno = errno;
        _err = "set up pipes";
        goto err;
    }
    if (pipe (p_in) < 0)
    {
        _errno = errno;
        _err = "set up pipes";

        fd_close (p_int[0]);
        fd_close (p_int[1]);

        goto err;
    }
    else if (ndelay_on (p_in[1]) < 0 || coe (p_in[1]) < 0)
    {
        _errno = errno;
        _err = "set up pipes";

        fd_close (p_int[0]);
        fd_close (p_int[1]);
        fd_close (p_in[0]);
        fd_close (p_in[1]);

        goto err;
    }
    if (pipe (p_out) < 0)
    {
        _errno = errno;
        _err = "set up pipes";

        fd_close (p_int[0]);
        fd_close (p_int[1]);
        fd_close (p_in[0]);
        fd_close (p_in[1]);

        goto err;
    }
    else if (ndelay_on (p_out[0]) < 0 || coe (p_out[0]) < 0)
    {
        _errno = errno;
        _err = "set up pipes";

        fd_close (p_int[0]);
        fd_close (p_int[1]);
        fd_close (p_in[0]);
        fd_close (p_in[1]);
        fd_close (p_out[0]);
        fd_close (p_out[1]);

        goto err;
    }
    if (pipe (p_prg) < 0)
    {
        _errno = errno;
        _err = "set up pipes";

        fd_close (p_int[0]);
        fd_close (p_int[1]);
        fd_close (p_in[0]);
        fd_close (p_in[1]);
        fd_close (p_out[0]);
        fd_close (p_out[1]);

        goto err;
    }
    else if (ndelay_on (p_prg[0]) < 0 || coe (p_prg[0]) < 0)
    {
        _errno = errno;
        _err = "set up pipes";

        fd_close (p_int[0]);
        fd_close (p_int[1]);
        fd_close (p_in[0]);
        fd_close (p_in[1]);
        fd_close (p_out[0]);
        fd_close (p_out[1]);
        fd_close (p_prg[0]);
        fd_close (p_prg[1]);

        goto err;
    }

    pid = fork ();
    if (pid < 0)
    {
        _errno = errno;
        _err = "fork";

        fd_close (p_int[1]);
        fd_close (p_int[0]);
        fd_close (p_in[0]);
        fd_close (p_in[1]);
        fd_close (p_out[1]);
        fd_close (p_out[0]);
        fd_close (p_prg[1]);
        fd_close (p_prg[0]);

        goto err;
    }
    else if (pid == 0)
    {
        char * const argv[] = { filename, NULL };
        PROG = aa_service_name (s);
        char buf_e[UINT32_FMT];
        uint32_t e;

        selfpipe_finish ();
        /* Ignore SIGINT to make sure one can ^C to timeout a service without
         * issue. */
        sig_ignore (SIGINT);
        fd_close (p_int[0]);
        fd_close (p_in[1]);
        fd_close (p_out[0]);
        fd_close (p_prg[0]);
        fd_close (0);
        fd_close (1);
        fd_close (2);
        if (fd_move (0, p_in[0]) < 0 || fd_move (1, p_out[1]) < 0
                || fd_copy (2, 1) < 0 || fd_move (3, p_prg[1]) < 0)
        {
            e = (uint32_t) errno;
            fd_write (p_int[1], "p", 1);
            uint32_pack (buf_e, e);
            fd_write (p_int[1], buf_e, UINT32_FMT);
            aa_strerr_diefu1sys (RC_FATAL_IO, "set up pipes");
        }

        if (chdir (PROG) < 0)
        {
            e = (uint32_t) errno;
            fd_write (p_int[1], "c", 1);
            uint32_pack (buf_e, e);
            fd_write (p_int[1], buf_e, UINT32_FMT);
            aa_strerr_diefu1sys (RC_FATAL_IO, "get into service directory");
        }

        buf[l_sn - 1] = '.';
        execv (buf + l_sn - 1, argv);
        /* if it fails... */
        e = (uint32_t) errno;
        fd_write (p_int[1], "e", 1);
        uint32_pack (buf_e, e);
        fd_write (p_int[1], buf_e, UINT32_FMT);
        aa_strerr_dieexec (RC_FATAL_EXEC, filename);
    }

    fd_close (p_int[1]);
    fd_close (p_in[0]);
    fd_close (p_out[1]);
    fd_close (p_prg[1]);
    switch (fd_read (p_int[0], &c, 1))
    {
        case 0:     /* it worked */
            {
                fd_close (p_int[0]);
                s->fd_in = p_in[1];
                s->fd_out = p_out[0];
                s->fd_progress = p_prg[0];

                tain_now_g ();

                if (_exec_cb)
                    _exec_cb (si, s->st.event, pid);
                return 0;
            }

        case 1:     /* child failed to exec */
            {
                char msg[l_fn + 260];
                char buf[UINT32_FMT];
                uint32_t e = 0;
                size_t p = 0;
                size_t l;

                tain_now_g ();

                if (fd_read (p_int[0], buf, UINT32_FMT) == UINT32_FMT)
                    uint32_unpack (buf, &e);
                fd_close (p_int[0]);
                fd_close (p_in[1]);
                fd_close (p_out[0]);
                fd_close (p_prg[0]);

                if (c == 'e')
                {
                    s->st.code = ERR_EXEC;
                    byte_copy (msg, l_fn - 1, filename);
                    p += l_fn - 1;
                }
                else if (c == 'p')
                    s->st.code = ERR_PIPES;
                else /* 'c' */
                    s->st.code = ERR_CHDIR;

                if (e > 0)
                {
                    if (c == 'e')
                    {
                        l = 2;
                        byte_copy (msg + p, l, ": ");
                        p += l;
                    }
                    l = strlen (strerror (e));
                    if (p + l >= 260)
                        l = 260 - p - 1;
                    byte_copy (msg + p, l, strerror (e));
                    p += l;
                }
                byte_copy (msg + p, 1, "");

                s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
                tain_copynow (&s->st.stamp);
                aa_service_status_set_msg (&s->st, msg);
                if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
                    aa_strerr_warnu2sys ("write service status file for ", aa_service_name (s));

                if (_exec_cb)
                    _exec_cb (si, s->st.event, 0);
                return -1;
            }

        case -1:    /* internal failure */
            _errno = errno;
            _err = "read pipe";

            fd_close (p_int[0]);
            fd_close (p_in[1]);
            fd_close (p_out[0]);
            fd_close (p_prg[0]);
    }

err:
    tain_now_g ();
    s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
    s->st.code = ERR_IO;
    tain_copynow (&s->st.stamp);
    {
        size_t l_ft  = strlen ("Failed to ");
        size_t l_err = strlen (_err);
        size_t l_buf = strlen (buf);
        const char *errstr = strerror (_errno);
        size_t l_es = strlen (errstr);
        char msg[l_ft + l_err + l_buf + 2 + l_es + 1];

        byte_copy (msg, l_ft, "Failed to ");
        byte_copy (msg + l_ft, l_err, _err);
        if (*_err == 's') /* stat */
            byte_copy (msg + l_ft + l_err, l_buf, buf);
        else
            l_buf = 0;
        byte_copy (msg + l_ft + l_err + l_buf, 2, ": ");
        byte_copy (msg + l_ft + l_err + l_buf + 2, l_es + 1, errstr);
        aa_service_status_set_msg (&s->st, msg);
    }
    if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
        aa_strerr_warnu2sys ("write service status file for ", aa_service_name (s));

    if (_exec_cb)
        _exec_cb (si, s->st.event, 0);
    return -1;
}