1
0
mirror of https://github.com/Llewellynvdm/conky.git synced 2024-11-16 18:15:17 +00:00
conky/src/exec.cc
Marc Payne adae1f6293 Refactor the exec callback system
The main purpose of this commit is to ensure that exec callbacks are
registered at startup (or config reload), so that they have a chance to
run on the first update interval. Much of the code in exec.cc got
consolidated, making it easier to follow. Several checks for NULL
pointers should eliminate some obscure segfaults I noticed, as well.

During the refactoring, I implemented height and width arguments for
execbar and execgauge. The functionality was already there, but wasn't
being used. All this is accompanied by updates to the man page and
plenty of code comments.
2016-01-09 10:19:48 -07:00

350 lines
9.6 KiB
C++

/* -*- mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
* vim: ts=4 sw=4 noet ai cindent syntax=cpp
*
* Conky, a system monitor, based on torsmo
*
* Any original torsmo code is licensed under the BSD license
*
* All code written since the fork of torsmo is licensed under the GPL
*
* Please see COPYING for details
*
* Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
* Copyright (c) 2005-2012 Brenden Matthews, Philip Kovacs, et. al.
* (see AUTHORS)
* All rights reserved.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "conky.h"
#include "core.h"
#include "exec.h"
#include "logging.h"
#include "specials.h"
#include "text_object.h"
#include "update-cb.hh"
#include <cmath>
#include <mutex>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
struct execi_data {
float interval;
char *cmd;
execi_data() : interval(0), cmd(0) {}
};
//our own implementation of popen, the difference : the value of 'childpid' will be filled with
//the pid of the running 'command'. This is useful if want to kill it when it hangs while reading
//or writing to it. We have to kill it because pclose will wait until the process dies by itself
static FILE* pid_popen(const char *command, const char *mode, pid_t *child) {
int ends[2];
int parentend, childend;
//by running pipe after the strcmp's we make sure that we don't have to create a pipe
//and close the ends if mode is something illegal
if (strcmp(mode, "r") == 0) {
if (pipe(ends) != 0) {
return NULL;
}
parentend = ends[0];
childend = ends[1];
} else if (strcmp(mode, "w") == 0) {
if (pipe(ends) != 0) {
return NULL;
}
parentend = ends[1];
childend = ends[0];
} else {
return NULL;
}
*child = fork();
if (*child == -1) {
close(parentend);
close(childend);
return NULL;
} else if (*child > 0) {
close(childend);
waitpid(*child, NULL, 0);
} else {
//don't read from both stdin and pipe or write to both stdout and pipe
if (childend == ends[0]) {
close(0);
} else {
close(1);
}
close(parentend);
//by dupping childend, the returned fd will have close-on-exec turned off
if (dup(childend) == -1)
perror("dup()");
close(childend);
execl("/bin/sh", "sh", "-c", command, (char *) NULL);
_exit(EXIT_FAILURE); //child should die here, (normally execl will take care of this but it can fail)
}
return fdopen(parentend, mode);
}
/**
* Executes a command and stores the result
*
* This function is called automatically, either once every update
* interval, or at specific intervals in the case of execi commands.
* conky::run_all_callbacks() handles this. In order for this magic to
* happen, we must register a callback with conky::register_cb<exec_cb>()
* and store it somewhere, such as obj->exec_handle. To retrieve the
* results, use the stored callback to call get_result_copy(), which
* returns a std::string.
*/
void exec_cb::work()
{
pid_t childpid;
std::string buf;
std::shared_ptr<FILE> fp;
char b[0x1000];
if (FILE *t = pid_popen(std::get<0>(tuple).c_str(), "r", &childpid))
fp.reset(t, fclose);
else
return;
while (!feof(fp.get()) && !ferror(fp.get())) {
int length = fread(b, 1, sizeof b, fp.get());
buf.append(b, length);
}
if (*buf.rbegin() == '\n')
buf.resize(buf.size()-1);
std::lock_guard<std::mutex> l(result_mutex);
result = buf;
}
//remove backspaced chars, example: "dog^H^H^Hcat" becomes "cat"
//string has to end with \0 and it's length should fit in a int
#define BACKSPACE 8
static void remove_deleted_chars(char *string){
int i = 0;
while (string[i] != 0) {
if (string[i] == BACKSPACE) {
if (i != 0) {
strcpy( &(string[i-1]), &(string[i+1]) );
i--;
} else strcpy( &(string[i]), &(string[i+1]) ); //necessary for ^H's at the start of a string
} else i++;
}
}
/**
* Parses command output to find a number between 0.0 and 100.0.
* Used by ${exec[i]{bar,gauge,graph}}.
*
* @param[in] buf output of a command executed by an exec_cb object
* @return number between 0.0 and 100.0
*/
static inline double get_barnum(const char *buf)
{
double barnum;
if (sscanf(buf, "%lf", &barnum) != 1) {
NORM_ERR("reading exec value failed (perhaps it's not the "
"correct format?)");
return 0.0;
}
if (barnum > 100.0 || barnum < 0.0) {
NORM_ERR("your exec value is not between 0 and 100, "
"therefore it will be ignored");
return 0.0;
}
return barnum;
}
/**
* Store command output in p. For execp objects, we process the output
* in case it contains special commands like ${color}
*
* @param[in] buffer the output of a command
* @param[in] obj text_object that specifies whether or not to parse
* @param[out] p the string in which we store command output
* @param[in] p_max_size the maximum size of p...
*/
void fill_p(const char *buffer, struct text_object *obj, char *p, int p_max_size) {
if (obj->parse == true) {
evaluate(buffer, p, p_max_size);
} else {
snprintf(p, p_max_size, "%s", buffer);
}
remove_deleted_chars(p);
}
/**
* Parses arg to find the command to be run, as well as special options
* like height, width, color, and update interval
*
* @param[out] obj stores the command and an execi_data structure (if applicable)
* @param[in] arg the argument to an ${exec*} object
* @param[in] execflag bitwise flag used to specify the exec variant we need to process
*/
void scan_exec_arg(struct text_object *obj, const char *arg, unsigned int execflag)
{
const char *cmd = arg;
struct execi_data *ed;
/* in case we have an execi object, we need to parse out the interval */
if (execflag & EF_EXECI) {
ed = new execi_data;
int n;
/* store the interval in ed->interval */
if (sscanf(arg, "%f %n", &ed->interval, &n) <= 0) {
NORM_ERR("missing execi interval: ${execi* <interval> command}");
delete ed;
ed = NULL;
return;
}
/* set cmd to everything after the interval */
cmd = strndup(arg + n, text_buffer_size.get(*state));
}
/* parse any special options for the graphical exec types */
if (execflag & EF_BAR) {
cmd = scan_bar(obj, cmd, 100);
#ifdef BUILD_X11
} else if (execflag & EF_GAUGE) {
cmd = scan_gauge(obj, cmd, 100);
} else if (execflag & EF_GRAPH) {
cmd = scan_graph(obj, cmd, 100);
if (!cmd) {
NORM_ERR("error parsing arguments to execgraph object");
}
#endif /* BUILD_X11 */
}
/* finally, store the resulting command, or an empty string if something went wrong */
if (execflag & EF_EXEC) {
obj->data.s = strndup(cmd ? cmd : "", text_buffer_size.get(*state));
} else if (execflag & EF_EXECI) {
ed->cmd = strndup(cmd ? cmd : "", text_buffer_size.get(*state));
obj->data.opaque = ed;
}
}
/**
* Register an exec_cb object using the command that we have parsed
*
* @param[out] obj stores the callback handle
*/
void register_exec(struct text_object *obj)
{
if (obj->data.s && obj->data.s[0]) {
obj->exec_handle = new conky::callback_handle<exec_cb>(
conky::register_cb<exec_cb>(1, true, obj->data.s));
} else {
DBGP("unable to register exec callback");
}
}
/**
* Register an exec_cb object using the command that we have parsed.
*
* This version takes care of execi intervals. Note that we depend on
* obj->thread, so be sure to run this function *after* setting obj->thread.
*
* @param[out] obj stores the callback handle
*/
void register_execi(struct text_object *obj)
{
struct execi_data *ed = (struct execi_data *)obj->data.opaque;
if (ed && ed->cmd && ed->cmd[0]) {
uint32_t period = std::max(lround(ed->interval/active_update_interval()), 1l);
obj->exec_handle = new conky::callback_handle<exec_cb>(
conky::register_cb<exec_cb>(period, !obj->thread, ed->cmd));
} else {
DBGP("unable to register execi callback");
}
}
/**
* Get the results of an exec_cb object (command output)
*
* @param[in] obj holds an exec_handle, assuming one was registered
* @param[out] p the string in which we store command output
* @param[in] p_max_size the maximum size of p...
*/
void print_exec(struct text_object *obj, char *p, int p_max_size)
{
if (obj->exec_handle) {
fill_p((*obj->exec_handle)->get_result_copy().c_str(), obj, p, p_max_size);
}
}
/**
* Get the results of a graphical (bar, gauge, graph) exec_cb object
*
* @param[in] obj hold an exec_handle, assuming one was registered
* @return a value between 0.0 and 100.0
*/
double execbarval(struct text_object *obj)
{
if (obj->exec_handle) {
return get_barnum((*obj->exec_handle)->get_result_copy().c_str());
} else {
return 0.0;
}
}
/**
* Free up any dynamically allocated data
*
* @param[in] obj holds the data that we need to free up
*/
void free_exec(struct text_object *obj)
{
free_and_zero(obj->data.s);
delete obj->exec_handle;
obj->exec_handle = NULL;
}
/**
* Free up any dynamically allocated data, specifically for execi objects
*
* @param[in] obj holds the data that we need to free up
*/
void free_execi(struct text_object *obj)
{
struct execi_data *ed = (struct execi_data *)obj->data.opaque;
/* if ed is NULL, there is nothing to do */
if (!ed)
return;
delete obj->exec_handle;
obj->exec_handle = NULL;
free_and_zero(ed->cmd);
delete ed;
ed = NULL;
obj->data.opaque = NULL;
}