/* Chrysalide - Outil d'analyse de fichiers binaires
 * io.c - entrées sorties fiables
 *
 * Copyright (C) 2014-2017 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  Chrysalide 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.
 *
 *  Chrysalide 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 Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "io.h"


#include <errno.h>
#include <libgen.h>
#include <malloc.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>


#include "../core/logs.h"
#include "../core/params.h"



/******************************************************************************
*                                                                             *
*  Paramètres  : fd    = flux ouvert en lecture.                              *
*                buf   = données à recevoir.                                  *
*                count = quantité de ces données.                             *
*                                                                             *
*  Description : Lit des données depuis un flux local.                        *
*                                                                             *
*  Retour      : true si toutes les données ont été lues, false sinon.        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool safe_read(int fd, void *buf, size_t count)
{
    uint8_t *iter;                          /* Données en attente          */
    size_t remaining;                       /* Quantité restante           */
    ssize_t got;                            /* Données envoyées            */

    iter = (uint8_t *)buf;
    remaining = count;

    while (remaining > 0)
    {
        got = read(fd, iter, remaining);

        if (got == -1)
        {
            if (errno == EINTR) continue;
            else
            {
                perror("read");
                break;
            }
        }

        if (got == 0)
            break;

        iter += got;
        remaining -= got;

    }

    return (remaining == 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : fd  = flux ouvert en lecture.                                *
*                buf = données à recevoir.                                    *
*                max = quantité maximale de ces données.                      *
*                                                                             *
*  Description : Lit des données depuis un flux local.                        *
*                                                                             *
*  Retour      : Nombre d'octets lus, au pire 0 en cas d'erreur.              *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

ssize_t safe_read_partial(int fd, void *buf, size_t max)
{
    ssize_t result;                         /* Quantité lue à remonter     */
    uint8_t *iter;                          /* Données en attente          */
    size_t remaining;                       /* Quantité restante           */
    ssize_t got;                            /* Données envoyées            */

    result = 0;

    iter = (uint8_t *)buf;
    remaining = max;

    while (remaining > 0)
    {
        got = read(fd, iter, remaining);

        if (got == -1)
        {
            if (errno == EINTR) continue;
            else
            {
                perror("read");
                break;
            }
        }

        if (got == 0)
            break;

        result += got;

        iter += got;
        remaining -= got;

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : fd    = flux ouvert en écriture.                             *
*                buf   = données à émettre.                                   *
*                count = quantité de ces données.                             *
*                                                                             *
*  Description : Ecrit des données dans un flux local.                        *
*                                                                             *
*  Retour      : true si toutes les données ont été écrites, false sinon.     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool safe_write(int fd, const void *buf, size_t count)
{
    uint8_t *iter;                          /* Données en attente          */
    size_t remaining;                       /* Quantité restante           */
    ssize_t sent;                           /* Données envoyées            */

    iter = (uint8_t *)buf;
    remaining = count;

    while (remaining > 0)
    {
        sent = write(fd, iter, remaining);

        if (sent == -1)
        {
            if (errno == EINTR) continue;
            else
            {
                perror("write");
                break;
            }
        }

        if (sent == 0)
            break;

        iter += sent;
        remaining -= sent;

    }

    return (remaining == 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : sockfd = flux ouvert en lecture.                             *
*                buf    = données à recevoir.                                 *
*                len    = quantité de ces données.                            *
*                flags  = options de réception.                               *
*                                                                             *
*  Description : Réceptionne des données depuis un flux réseau.               *
*                                                                             *
*  Retour      : true si toutes les données ont été reçues, false sinon.      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool safe_recv(int sockfd, const void *buf, size_t len, int flags)
{
    uint8_t *iter;                          /* Données en attente          */
    size_t remaining;                       /* Quantité restante           */
    ssize_t got;                            /* Données envoyées            */

    iter = (uint8_t *)buf;
    remaining = len;

    while (remaining > 0)
    {
        got = recv(sockfd, iter, remaining, MSG_NOSIGNAL | flags);
        if (got == -1)
        {
            if (errno == EINTR) continue;
            else
            {
                perror("recv");
                break;
            }
        }

        iter += got;
        remaining -= got;

    }

    return (remaining == 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : sockfd = flux ouvert en écriture.                            *
*                buf    = données à émettre.                                  *
*                len    = quantité de ces données.                            *
*                flags  = options d'envoi.                                    *
*                                                                             *
*  Description : Envoie des données au travers un flux réseau.                *
*                                                                             *
*  Retour      : true si toutes les données ont été émises, false sinon.      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool safe_send(int sockfd, const void *buf, size_t len, int flags)
{
    uint8_t *iter;                          /* Données en attente          */
    size_t remaining;                       /* Quantité restante           */
    ssize_t sent;                           /* Données envoyées            */

    iter = (uint8_t *)buf;
    remaining = len;

    while (remaining > 0)
    {
        sent = send(sockfd, iter, remaining, MSG_NOSIGNAL | flags);
        if (sent == -1)
        {
            if (errno == EINTR) continue;
            else
            {
                perror("send");
                break;
            }
        }

        iter += sent;
        remaining -= sent;

    }

    return (remaining == 0);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : path = chemin d'accès à valider.                             *
*                                                                             *
*  Description : S'assure qu'un chemin donné existe dans le système.          *
*                                                                             *
*  Retour      : 0 si le chemin est actuellement présent, -1 sinon.           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

int ensure_path_exists(const char *path)
{
    int result;                             /* Bilan de l'assurance        */
    char *copy;                             /* Chemin libérable            */
    char *tmp;                              /* Chemin altérable            */

    copy = strdup(path);
    tmp = dirname(copy);

    result = access(tmp, W_OK | X_OK);

    if (result != 0)
    {
        result = ensure_path_exists(tmp);

        if (result == 0)
            result = mkdir(tmp, 0700);

    }

    free(copy);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : base     = préfixe du nom du fichier temporaire à créer.     *
*                filename = chemin d'accès complet au nouveau fichier. [OUT]  *
*                                                                             *
*  Description : Met en place un fichier temporaire.                          *
*                                                                             *
*  Retour      : Flux ouvert en lecture et écriture, ou -1 en cas d'erreur.   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

int make_tmp_file(const char *base, char **filename)
{
    int result;                             /* Flux ou code à retourner    */
    const char *tmpdir;                     /* Répertoire d'accueil        */
    bool status;                            /* Bilan d'un consultation     */

    status = g_generic_config_get_value(get_main_configuration(), MPK_TMPDIR, &tmpdir);
    if (!status) return -1;

    asprintf(filename, "%s" G_DIR_SEPARATOR_S "%s-%d.XXXXXX", tmpdir, base, getpid());

    result = ensure_path_exists(*filename);

    if (result == 0)
    {
        result = mkstemp(*filename);
        if (result == -1) perror("mkstemp");
    }

    if (result == -1)
    {
        free(*filename);
        *filename = NULL;
    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : base = préfixe du nom du fichier temporaire à créer.         *
*                addr = adresse UNIX constituée. [OUT]                        *
*                                                                             *
*  Description : Met en place un canal UNIX temporaire.                       *
*                                                                             *
*  Retour      : Bilan de la définition.                                      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool build_tmp_socket(const char *base, struct sockaddr_un *addr)
{
    bool result;                            /* Bilan à retourner           */
    const char *tmpdir;                     /* Répertoire d'accueil        */
    bool status;                            /* Bilan d'un consultation     */
    char *path;                             /* Chemin d'accès au canal     */
    int ret;                                /* Bilan intermédiaire         */
    size_t length;                          /* Taille du chemin complet    */

    status = g_generic_config_get_value(get_main_configuration(), MPK_TMPDIR, &tmpdir);
    if (!status) return false;

    result = false;

    asprintf(&path, "%s" G_DIR_SEPARATOR_S "%s-%d", tmpdir, base, getpid());

    ret = ensure_path_exists(path);
    if (ret != 0) goto mts_exit;

    length = strlen(path) + 1;

#ifndef UNIX_PATH_MAX
#   define UNIX_PATH_MAX 108
#endif

    if (length > UNIX_PATH_MAX)
    {
        log_variadic_message(LMT_ERROR,
                             _("Impossible to use '%s' as UNIX socket path: string is too long ! (%zu vs %u)\n"),
                             path, length, UNIX_PATH_MAX);
        goto mts_exit;
    }

    memset(addr, 0, sizeof(struct sockaddr_un));

    addr->sun_family = AF_UNIX;
    strncpy(addr->sun_path, path, UNIX_PATH_MAX - 1);

    result = true;

 mts_exit:

    free(path);

    return result;

}