From 428f4208552973d70471f1bb7705f0c2b11ba2d1 Mon Sep 17 00:00:00 2001 From: Kostyantyn Vorobyov <kostyantyn.vorobyov@cea.fr> Date: Mon, 18 Jul 2016 10:52:12 +0200 Subject: [PATCH] [RTL] Interface for running shell commands --- .../e-acsl/share/e-acsl/e_acsl_shexec.h | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 src/plugins/e-acsl/share/e-acsl/e_acsl_shexec.h diff --git a/src/plugins/e-acsl/share/e-acsl/e_acsl_shexec.h b/src/plugins/e-acsl/share/e-acsl/e_acsl_shexec.h new file mode 100644 index 00000000000..9c82ecb2428 --- /dev/null +++ b/src/plugins/e-acsl/share/e-acsl/e_acsl_shexec.h @@ -0,0 +1,225 @@ +/**************************************************************************/ +/* */ +/* This file is part of the Frama-C's E-ACSL plug-in. */ +/* */ +/* Copyright (C) 2012-2015 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It 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 Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file license/LGPLv2.1). */ +/* */ +/**************************************************************************/ + +/*! *********************************************************************** + * \file e_acsl_shexec.h + * \brief Interface for running shell commands +***************************************************************************/ + +#ifndef E_ACSL_SHEXEC +#define E_ACSL_SHEXEC + +#include "e_acsl_malloc.h" +#include "e_acsl_printf.h" +#include "e_acsl_string.h" +#include <errno.h> +#include <sys/wait.h> + +/*! \class ipr_t + * \brief Result struct for `shexec` function -- execute a command in the + * shell via fork/exec and return results */ +typedef struct { + /** \brief resulting STDERR stream as \p const \p char* */ + char *stderrs; + /** \brief Supplied STDIN stream as \p const \p char* */ + char *stdins; + /** \brief resulting STDOUT stream as \p const \p char* */ + char *stdouts; + /** \brief Exit status of a program */ + int exit_status; + /** \brief ID of a child process this command has been executed in */ + pid_t pid; + /** \brief Set to non-zero if child process is interrupted via a signal */ + int signaled; + /** \brief If \p signalled is set, \p signo is set to the number of signal + * that interrupted execution of a child process */ + int signo; + /** \brief A command to execute. Needs to be NULL terminated */ + char **argv; /** \brief ARGV */ + /** \brief Message if the command has failed to run */ + char *error; +} ipr_t; + +/* \brief Read characters from a buffer associated with a file descriptor into + * a C string + * + * Read a string from a buffer associated with a file descriptor by allocating + * a string of an initial size and increasing the size of a buffer with + * realloc. This is for the cases when we can not quickly seek through the + * file and identify the size of a buffer associated with a file descriptor + * + * \param fd - file descriptor to read from + * \param bufsize - the number of characters we read a t a time + * + * \return NUL-terminated C string on success + * \return NULL on failure */ +char* fd_read (int fd, short bufsize) { + /* Read `buffer_size` chars at a time */ + short buffer_size = bufsize*sizeof(char); + /* Size of the fetched string */ + int size = buffer_size; + /* Buffer where for read data */ + char *buffer = (char*)native_malloc(size); + /* The number of read bytes */ + short fetched = 0; + int rd = 0; /* Count of fetched characters */ + + /* Each time the pointer if moved by `size - buffer_size` as initially + * the size of 'buffer' is 'buffer_size' */ + while ((fetched = read(fd, buffer + size - buffer_size, buffer_size))) { + rd += fetched; + if (fetched != -1) { + size += fetched*sizeof(char); + buffer = native_realloc(buffer, size + 1); + } else { + return NULL; + } + } + buffer[rd] = '\0'; + return buffer; +} + +/* Execute a command in the shell and place results to data */ +ipr_t* __shexec (ipr_t *data) { + int outfd[2], errfd[2], infd[2]; + int oldstdout, oldstderr, oldstdin; + + if (pipe(infd)) /* From where parent is going to read */ + data->error = nstrdup("Can not create a pipe for STDIN"); + if (pipe(outfd)) /* From where parent is going to read */ + data->error = nstrdup("Can not create a pipe for STDOUT"); + if (pipe(errfd)) /* From where parent is going to read */ + data->error = nstrdup("Can not create a pipe for STDERR"); + + /* Immediately return if reading from one of the STD pipes failed */ + if (data->error) + return data; + + /* Save stdin, stdout and stderr */ + oldstdin = dup(0); + oldstdout = dup(1); + oldstderr = dup(2); + + /* Close stdin, stdout and stderr */ + close(0); + close(1); + close(2); + + dup2(infd[0], 0); /* Make the read end of infd as STDIN */ + dup2(outfd[1],1); /* Make the write end of outfd as STDOUT */ + dup2(errfd[1],2); /* Make the write end of outfd as STDERR */ + + pid_t pid = fork(); + + if(!pid) { + /* Close the streams as they are not required for a child */ + close(infd[0]); close(outfd[0]); close(errfd[0]); + close(infd[1]); close(outfd[1]); close(errfd[1]); + + execvp(data->argv[0],data->argv); + dup2(oldstderr,2); /* Restore stderr */ + if (errno) { + data->error = nstrdup("Failed to execute:\n "); + char **arg = data->argv - 1; + while(*++arg) + data->error = sappend(*arg, data->error, " "); + } + } else { + close(0); + close(1); + close(2); + dup2(oldstdin, 0); + dup2(oldstdout,1); + dup2(oldstderr,2); + close(outfd[1]); + close(errfd[1]); + close(infd[0]); + + /* If data->stdin string if supplied, write that string to child's + STDIN first */ + if (data->stdins) /* Return NULL if write fails */ + if (write(infd[1], data->stdins, strlen(data->stdins)) == -1) + return NULL; + + /* Read from child's stdout and stderr */ + data->stdouts = fd_read(outfd[0], 256); + if (!data->stdouts) + data->error = nstrdup("Error reading from STDOUT pipe"); + data->stderrs = fd_read(errfd[0], 256); + if (!data->stderrs) + data->error = nstrdup("Error reading from STDERR pipe"); + + /* Close file descriptors we still have open */ + close(outfd[0]); /* read end of STDOUT */ + close(errfd[0]); /* read end of STDERR */ + close(infd[1]); /* write end of STDIN */ + + int status; + waitpid(pid,&status,0); /* wait for the child to finish */ + + data->exit_status = WEXITSTATUS(status); /* exis status */ + data->pid = pid; /* process number */ + data->signaled = WIFSIGNALED(status); /* signal caught */ + data->signo = WTERMSIG(status); /* signal number caught */ + return data; + } + return NULL; +} + +/* \brief Deallocate an `ipr_t` structure returned by `shexec` */ +void free_ipr (ipr_t* ipr) { + if (ipr) { + if (ipr->stdouts) + native_free(ipr->stdouts); + if (ipr->stderrs) + native_free(ipr->stderrs); + if (ipr->error) + native_free(ipr->error); + if (ipr->stdins) + native_free(ipr->stdins); + native_free(ipr); + } +} + +/* \brief Execute a command given via parameter `data` in the current shell + * and its result as `ipr_t` struct. + * + * \param data - command to execute. This argument is assumed to be a + * NULL-terminated array of strings + * \param sin - if not NULL a C string given via `sin` is supplied as standard + * input to the executed command + * \return - heap-allocated struct `ipr_t` that describes the output of the + * executed command. De-allocation of this struct should be performed via the + * `free_ipr` function */ +static ipr_t* shexec (char **data, const char *sin) { + /* Allocate and initialise the struct that we use */ + ipr_t *ipr = (ipr_t*)native_malloc(sizeof(ipr_t)); + ipr->stderrs = NULL; + ipr->stdouts = NULL; + ipr->stdins = nstrdup(sin); + ipr->argv = data; + ipr->exit_status = 0; + ipr->pid = 0; + ipr->signaled = 0; + return __shexec(ipr); +} +#endif -- GitLab