Welcome to little lamb

Code » anopa » master » tree

[master] / src / libanopa / exec_longrun.c

/*
 * anopa - Copyright (C) 2015-2017 Olivier Brunel
 *
 * exec_longrun.c
 * Copyright (C) 2015-2017 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 <unistd.h>
#include <strings.h>
#include <errno.h>
#include <skalibs/djbunix.h>
#include <skalibs/bytestr.h>
#include <skalibs/tai.h>
#include <s6/s6-supervise.h>
#include <s6/ftrigr.h>
#include <anopa/service.h>
#include <anopa/err.h>
#include <anopa/output.h>
#include "service_internal.h"

int
_exec_longrun (int si, aa_mode mode)
{
    aa_service *s = aa_service (si);
    s6_svstatus_t st6 = S6_SVSTATUS_ZERO;
    size_t l_sn = strlen (aa_service_name (s));
    char fifodir[l_sn + 1 + sizeof (S6_SUPERVISE_EVENTDIR)];
    tain_t deadline;
    int is_start = (mode & AA_MODE_START) ? 1 : 0;
    const char *event = (is_start) ? ((s->gets_ready) ? "[udU]" : "u") : "D";
    const char *cmd = (is_start) ? "u" : (mode & AA_MODE_STOP_ALL) ? "dx" : "d";
    int already = 0;

    byte_copy (fifodir, l_sn, aa_service_name (s));
    fifodir[l_sn] = '/';
    byte_copy (fifodir + l_sn + 1, sizeof (S6_SUPERVISE_EVENTDIR), S6_SUPERVISE_EVENTDIR);

    tain_addsec_g (&deadline, 1);
    s->ft_id = ftrigr_subscribe_g (&_aa_ft, fifodir, event,
                                   (*event == '[') ? FTRIGR_REPEAT : 0,
                                   &deadline);
    if (s->ft_id == 0)
    {
        /* this could happen e.g. if the servicedir isn't in scandir, if
         * something failed during aa-enable for example */

        const char *errmsg = "Failed to subscribe to eventdir: ";
        size_t l_msg = strlen (errmsg);
        const char *errstr = strerror (errno);
        size_t l_err = strlen (errstr);
        char msg[l_msg + l_err + 1];

        byte_copy (msg, l_msg, errmsg);
        byte_copy (msg + l_msg, l_err + 1, errstr);

        s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
        s->st.code = ERR_S6;
        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;
    }

    if (s6_svstatus_read (aa_service_name (s), &st6)
            && ((is_start && (st6.pid && !st6.flagfinishing))
                || (!is_start && !(st6.pid && !st6.flagfinishing))))
    {
        tain_now_g ();

        if (!is_start || !s->gets_ready || st6.flagready)
        {
            /* already there: unsubcribe.
             *
             * Failure to unsubscribe w/ EINVAL means that the service got where
             * we wanted *after* we subscribed, and the event was already
             * treated/is queued up (when non-repeating, i.e. no FTRIGR_REPEAT).
             * Then, we want to process it later as usual (i.e. keep it & not
             * set already).
             */
            if (aa_unsubscribe_for (s->ft_id) == 0 || errno != EINVAL)
            {
                already = 1;
                s->ft_id = 0;
            }
        }

        /* make sure our status is correct, and timestamped before s6 */
        s->st.event = (is_start) ? AA_EVT_STARTING : AA_EVT_STOPPING;
        tain_addsec (&s->st.stamp, &st6.stamp, -1);
        aa_service_status_set_msg (&s->st, "");
        if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
            aa_strerr_warnu2sys ("write service status file for ", aa_service_name (s));

        /* we still process it. Because we checked the service state (from s6)
         * before adding it to the "transaction" (i.e. in
         * aa_ensure_service_loaded()) and it wasn't there already.
         * IOW this is likely e.g. that it crashed since then, but it isn't
         * really down, or something. So make sure we do send the request to
         * s6-supervise, so it isn't restarted, or indeed brought down if it's
         * happening right now. Also in STOP_ALL this sends the 'x' cmd to
         * s6-supervise as needed as well.
         */

        if (is_start && s->gets_ready)
            /* However, on START sending the command is possibly problematic
             * regarding (externally set) readiness.
             * E.g. if the service is ready, and readiness was set externally
             * (e.g. via aa-setready) it would result in said readiness being
             * "lost" when s6-supervise rewrites the status file.
             * Similarly, if readiness was originally set via s6-supervise
             * (notification-fd) and then unset externally, it could come back
             * if s6-supervise was to rewrite the status file.
             */
            cmd = NULL;
    }
    else
    {
        tain_now_g ();

        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 (cmd)
    {
        char dir[l_sn + 1 + sizeof (S6_SUPERVISE_CTLDIR) + 8];
        int r;

        byte_copy (dir, l_sn, aa_service_name (s));
        byte_copy (dir + l_sn, 9 + sizeof (S6_SUPERVISE_CTLDIR), "/" S6_SUPERVISE_CTLDIR "/control");

        r = s6_svc_write (dir, cmd, strlen (cmd));
        if (r <= 0 && !already)
        {
            aa_unsubscribe_for (s->ft_id);
            s->ft_id = 0;

            s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
            s->st.code = ERR_S6;
            tain_copynow (&s->st.stamp);
            aa_service_status_set_msg (&s->st, (r < 0)
                    ? "Failed to send command"
                    : "Supervisor not listenning");
            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;
        }
    }

    {
        char buf[l_sn + 6];

        byte_copy (buf, l_sn, aa_service_name (s));
        byte_copy (buf + l_sn, 6, "/down");

        if (is_start)
            unlink (buf);
        else
        {
            int fd;

            fd = open_create (buf);
            if (fd < 0)
                aa_strerr_warnu2sys ("create down file for ", aa_service_name (s));
            else
                fd_close (fd);
        }
    }

    if (already)
    {
        if (_exec_cb)
            _exec_cb (si, (is_start) ? -ERR_ALREADY_UP : -ERR_NOT_UP, 0);

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

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

static size_t idx = (size_t) -1;

int
aa_get_longrun_info (uint16_t *id, char *event)
{
    int r;

    if (idx == (size_t) -1)
    {
        r = ftrigr_update (&_aa_ft);
        if (r < 0)
            return -1;
        else if (r == 0)
            return 0;
        idx = 0;
    }

    for ( ; idx < genalloc_len (uint16_t, &_aa_ft.list); )
    {
        *id = genalloc_s (uint16_t, &_aa_ft.list)[idx];
        r = ftrigr_check (&_aa_ft, *id, event);
        ++idx;
        if (r != 0)
            return (r < 0) ? -2 : r;
    }

    idx = (size_t) -1;
    return 0;
}

int
aa_unsubscribe_for (uint16_t id)
{
    tain_t deadline;

    tain_addsec_g (&deadline, 1);
    return (ftrigr_unsubscribe_g (&_aa_ft, id, &deadline)) ? 0 : -1;
}