/* -*- 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-2010 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 .
*
*/
#include "conky.h"
#include "core.h"
#include "logging.h"
#include "specials.h"
#include "text_object.h"
#include "timed-thread.h"
#include
#include
#include
#include
#include
struct execi_data {
double last_update;
float interval;
char *cmd;
char *buffer;
double data;
timed_thread_ptr p_timed_thread;
float barnum;
execi_data() : last_update(0), interval(0), cmd(0), buffer(0), data(0), barnum(0) {}
};
/* FIXME: this will probably not work, since the variable is being reused
* between different text objects. So when a process really hangs, it's PID
* will be overwritten at the next iteration. */
pid_t childpid = 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);
}
//by dupping childend, the returned fd will have close-on-exec turned off
if (dup(childend) == -1)
perror("dup()");
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);
}
//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++;
}
}
static inline double get_barnum(char *buf)
{
char *c = buf;
double barnum;
while (*c) {
if (*c == '\001') {
*c = ' ';
}
c++;
}
if (sscanf(buf, "%lf", &barnum) == 0) {
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;
}
static inline void read_exec(const char *data, char *buf, const int size)
{
FILE *fp;
memset(buf, 0, size);
if (!data)
return;
alarm(update_interval);
fp = pid_popen(data, "r", &childpid);
if(fp) {
int length;
length = fread(buf, 1, size, fp);
pclose(fp);
buf[std::min(length, size-1)] = '\0';
if (length > 0 && buf[length - 1] == '\n') {
buf[length - 1] = '\0';
}
} else {
buf[0] = '\0';
}
alarm(0);
}
static double read_exec_barnum(const char *data)
{
char *buf = NULL;
double barnum;
buf = (char*)malloc(text_buffer_size);
read_exec(data, buf, text_buffer_size);
barnum = get_barnum(buf);
free(buf);
return barnum;
}
static void threaded_exec(thread_handle &handle, struct text_object *obj)
{
char *buff, *p2;
struct execi_data *ed = (struct execi_data *)obj->data.opaque;
while (1) {
buff = (char*)malloc(text_buffer_size);
read_exec(ed->cmd, buff, text_buffer_size);
p2 = buff;
while (*p2) {
if (*p2 == '\001') {
*p2 = ' ';
}
p2++;
}
{
std::lock_guard lock(handle.mutex());
free_and_zero(ed->buffer);
ed->buffer = buff;
}
if (handle.test(0)) {
return;
}
}
/* never reached */
}
/* check the execi fields and return true if the given interval has passed */
static int time_to_update(struct execi_data *ed)
{
if (!ed->interval)
return 0;
if (current_update_time - ed->last_update >= ed->interval)
return 1;
return 0;
}
void scan_exec_arg(struct text_object *obj, const char *arg)
{
/* XXX: do real bar parsing here */
scan_bar(obj, "", 100);
obj->data.s = strndup(arg ? arg : "", text_buffer_size);
}
void scan_pre_exec_arg(struct text_object *obj, const char *arg)
{
char buf[2048];
read_exec(arg, buf, sizeof(buf));
obj_be_plain_text(obj, buf);
}
void scan_execi_arg(struct text_object *obj, const char *arg)
{
struct execi_data *ed;
int n;
ed = new execi_data;
if (sscanf(arg, "%f %n", &ed->interval, &n) <= 0) {
NORM_ERR("${execi* command}");
delete ed;
return;
}
ed->cmd = strndup(arg + n, text_buffer_size);
obj->data.opaque = ed;
}
void scan_execi_bar_arg(struct text_object *obj, const char *arg)
{
/* XXX: do real bar parsing here */
scan_bar(obj, "", 100);
scan_execi_arg(obj, arg);
}
void scan_execi_gauge_arg(struct text_object *obj, const char *arg)
{
/* XXX: do real gauge parsing here */
scan_gauge(obj, "", 100);
scan_execi_arg(obj, arg);
}
#ifdef BUILD_X11
void scan_execgraph_arg(struct text_object *obj, const char *arg)
{
struct execi_data *ed;
char *buf;
ed = new execi_data;
memset(ed, 0, sizeof(struct execi_data));
buf = scan_graph(obj, arg, 100);
if (!buf) {
NORM_ERR("missing command argument to execgraph object");
return;
}
ed->cmd = buf;
obj->data.opaque = ed;
}
#endif /* BUILD_X11 */
void fill_p(char *buffer, struct text_object *obj, char *p, int p_max_size) {
if(obj->parse == true) {
struct text_object subroot;
parse_conky_vars(&subroot, buffer, p, p_max_size);
free_text_objects(&subroot);
} else snprintf(p, p_max_size, "%s", buffer);
remove_deleted_chars(p);
}
void print_exec(struct text_object *obj, char *p, int p_max_size)
{
char *buf;
buf = (char*)malloc(text_buffer_size);
memset(buf, 0, text_buffer_size);
read_exec(obj->data.s, buf, text_buffer_size);
fill_p(buf, obj, p, p_max_size);
free(buf);
}
void print_execi(struct text_object *obj, char *p, int p_max_size)
{
struct execi_data *ed = (struct execi_data *)obj->data.opaque;
bool fillbuffer = true;
if (!ed)
return;
if(obj->thread == true) {
if (!ed->p_timed_thread) {
/*
* note that we don't register this thread with the
* timed_thread list, because we destroy it manually
*/
ed->p_timed_thread = timed_thread::create(std::bind(threaded_exec, std::placeholders::_1, obj),
std::chrono::microseconds(long(ed->interval * 1000000)), false);
if (!ed->p_timed_thread) {
NORM_ERR("Error creating texeci timed thread");
}
fillbuffer = false;
} else {
std::lock_guard lock(ed->p_timed_thread->mutex());
}
} else {
if (time_to_update(ed)) {
if (!ed->buffer)
ed->buffer = (char*)malloc(text_buffer_size);
read_exec(ed->cmd, ed->buffer, text_buffer_size);
ed->last_update = current_update_time;
}
}
if(fillbuffer) fill_p(ed->buffer, obj, p, p_max_size);
}
double execbarval(struct text_object *obj)
{
return read_exec_barnum(obj->data.s);
}
double execi_barval(struct text_object *obj)
{
struct execi_data *ed = (struct execi_data *)obj->data.opaque;
if (!ed)
return 0;
if (time_to_update(ed)) {
ed->barnum = read_exec_barnum(ed->cmd);
ed->last_update = current_update_time;
}
return ed->barnum;
}
void free_exec(struct text_object *obj)
{
free_and_zero(obj->data.s);
}
void free_execi(struct text_object *obj)
{
struct execi_data *ed = (struct execi_data *)obj->data.opaque;
if (!ed)
return;
if (ed->p_timed_thread) {
ed->p_timed_thread.reset();
}
free_and_zero(ed->cmd);
free_and_zero(ed->buffer);
delete ed;
obj->data.opaque = NULL;
}