1 /*
2  *
3  * Conky, a system monitor, based on torsmo
4  *
5  * Any original torsmo code is licensed under the BSD license
6  *
7  * All code written since the fork of torsmo is licensed under the GPL
8  *
9  * Please see COPYING for details
10  *
11  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
12  * Copyright (c) 2005-2021 Brenden Matthews, Philip Kovacs, et. al.
13  *	(see AUTHORS)
14  * All rights reserved.
15  *
16  * This program is free software: you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation, either version 3 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  * You should have received a copy of the GNU General Public License
26  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27  *
28  */
29 
30 #include "exec.h"
31 #include <fcntl.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 #include <unistd.h>
35 #include <cmath>
36 #include <cstdio>
37 #include <mutex>
38 #include "conky.h"
39 #include "core.h"
40 #include "logging.h"
41 #include "specials.h"
42 #include "text_object.h"
43 #include "update-cb.hh"
44 
45 struct execi_data {
46   float interval{0};
47   char *cmd{nullptr};
48   execi_data() = default;
49 };
50 
51 static const int cmd_len = 256;
52 static char cmd[cmd_len];
53 
54 static char *remove_excess_quotes(const char *);
remove_excess_quotes(const char * command)55 static char *remove_excess_quotes(const char *command) {
56   char *cmd_ptr = cmd;
57   const char *command_ptr = command;
58   int skip = 0;
59 
60   if ((cmd_len - 1) < (strlen(command) - 1)) {
61     snprintf(cmd, cmd_len - 1, "%s", command);
62     return cmd;
63   }
64 
65   if (*command_ptr == '"' || *command_ptr == '\'') {
66     skip = 1;
67     command_ptr++;
68   }
69 
70   for (; *command_ptr; command_ptr++) {
71     if ('\0' == *(command_ptr + 1) && 1 == skip &&
72         (*command_ptr == '"' || *command_ptr == '\'')) {
73       continue;
74     }
75     *cmd_ptr++ = *command_ptr;
76   }
77   *cmd_ptr = '\0';
78   return cmd;
79 }
80 
81 // our own implementation of popen, the difference : the value of 'childpid'
82 // will be filled with the pid of the running 'command'. This is useful if want
83 // to kill it when it hangs while reading or writing to it. We have to kill it
84 // because pclose will wait until the process dies by itself
pid_popen(const char * command,const char * mode,pid_t * child)85 static FILE *pid_popen(const char *command, const char *mode, pid_t *child) {
86   int ends[2];
87   int parentend, childend;
88 
89   // by running pipe after the strcmp's we make sure that we don't have to
90   // create a pipe and close the ends if mode is something illegal
91   if (strcmp(mode, "r") == 0) {
92     if (pipe(ends) != 0) { return nullptr; }
93     parentend = ends[0];
94     childend = ends[1];
95   } else if (strcmp(mode, "w") == 0) {
96     if (pipe(ends) != 0) { return nullptr; }
97     parentend = ends[1];
98     childend = ends[0];
99   } else {
100     return nullptr;
101   }
102 
103   *child = fork();
104   if (*child == -1) {
105     close(parentend);
106     close(childend);
107     return nullptr;
108   }
109   if (*child > 0) {
110     close(childend);
111     waitpid(*child, nullptr, 0);
112   } else {
113     // don't read from both stdin and pipe or write to both stdout and pipe
114     if (childend == ends[0]) {
115       close(0);
116     } else {
117       close(1);
118     }
119     close(parentend);
120 
121     // by dupping childend, the returned fd will have close-on-exec turned off
122     if (fcntl(childend, F_DUPFD, 0) == -1) { perror("fcntl()"); }
123     close(childend);
124 
125     execl("/bin/sh", "sh", "-c", remove_excess_quotes(command),
126           (char *)nullptr);
127     _exit(EXIT_FAILURE);  // child should die here, (normally execl will take
128                           // care of this but it can fail)
129   }
130 
131   return fdopen(parentend, mode);
132 }
133 
134 /**
135  * Executes a command and stores the result
136  *
137  * This function is called automatically, either once every update
138  * interval, or at specific intervals in the case of execi commands.
139  * conky::run_all_callbacks() handles this. In order for this magic to
140  * happen, we must register a callback with conky::register_cb<exec_cb>()
141  * and store it somewhere, such as obj->exec_handle. To retrieve the
142  * results, use the stored callback to call get_result_copy(), which
143  * returns a std::string.
144  */
work()145 void exec_cb::work() {
146   pid_t childpid;
147   std::string buf;
148   std::shared_ptr<FILE> fp;
149   char b[0x1000];
150 
151   if (FILE *t = pid_popen(std::get<0>(tuple).c_str(), "r", &childpid)) {
152     fp.reset(t, fclose);
153   } else {
154     return;
155   }
156 
157   while ((feof(fp.get()) == 0) && (ferror(fp.get()) == 0)) {
158     int length = fread(b, 1, sizeof b, fp.get());
159     buf.append(b, length);
160   }
161 
162   if (*buf.rbegin() == '\n') { buf.resize(buf.size() - 1); }
163 
164   std::lock_guard<std::mutex> l(result_mutex);
165   result = buf;
166 }
167 
168 // remove backspaced chars, example: "dog^H^H^Hcat" becomes "cat"
169 // string has to end with \0 and it's length should fit in a int
170 #define BACKSPACE 8
remove_deleted_chars(char * string,unsigned int p_max_size)171 static void remove_deleted_chars(char *string, unsigned int p_max_size) {
172   int i = 0;
173   while (string[i] != 0) {
174     if (string[i] == BACKSPACE) {
175       if (i != 0) {
176         strncpy(&(string[i - 1]), &(string[i + 1]),
177                 strnlen(string, p_max_size) - i + 1);
178         i--;
179       } else {
180         strncpy(&(string[i]), &(string[i + 1]),
181                 strnlen(string, p_max_size) -
182                     i);  // necessary for ^H's at the start of a string
183       }
184     } else {
185       i++;
186     }
187   }
188 }
189 
190 /**
191  * Parses command output to find a number between 0.0 and 100.0.
192  * Used by ${exec[i]{bar,gauge,graph}}.
193  *
194  * @param[in] buf output of a command executed by an exec_cb object
195  * @return number between 0.0 and 100.0
196  */
get_barnum(const char * buf)197 static inline double get_barnum(const char *buf) {
198   double barnum;
199 
200   if (sscanf(buf, "%lf", &barnum) != 1) {
201     NORM_ERR(
202         "reading exec value failed (perhaps it's not the "
203         "correct format?)");
204     return 0.0;
205   }
206   if (barnum > 100.0 || barnum < 0.0) {
207     NORM_ERR(
208         "your exec value is not between 0 and 100, "
209         "therefore it will be ignored");
210     return 0.0;
211   }
212 
213   return barnum;
214 }
215 
216 /**
217  * Store command output in p. For execp objects, we process the output
218  * in case it contains special commands like ${color}
219  *
220  * @param[in] buffer the output of a command
221  * @param[in] obj text_object that specifies whether or not to parse
222  * @param[out] p the string in which we store command output
223  * @param[in] p_max_size the maximum size of p...
224  */
fill_p(const char * buffer,struct text_object * obj,char * p,unsigned int p_max_size)225 void fill_p(const char *buffer, struct text_object *obj, char *p,
226             unsigned int p_max_size) {
227   if (obj->parse) {
228     evaluate(buffer, p, p_max_size);
229   } else {
230     snprintf(p, p_max_size, "%s", buffer);
231   }
232 
233   remove_deleted_chars(p, p_max_size);
234 }
235 
236 /**
237  * Parses arg to find the command to be run, as well as special options
238  * like height, width, color, and update interval
239  *
240  * @param[out] obj stores the command and an execi_data structure (if
241  * applicable)
242  * @param[in] arg the argument to an ${exec*} object
243  * @param[in] execflag bitwise flag used to specify the exec variant we need to
244  * process
245  */
scan_exec_arg(struct text_object * obj,const char * arg,unsigned int execflag)246 void scan_exec_arg(struct text_object *obj, const char *arg,
247                    unsigned int execflag) {
248   const char *cmd = arg;
249   char *orig_cmd = nullptr;
250   struct execi_data *ed;
251 
252   /* in case we have an execi object, we need to parse out the interval */
253   if ((execflag & EF_EXECI) != 0u) {
254     ed = new execi_data;
255     int n;
256 
257     /* store the interval in ed->interval */
258     if (sscanf(arg, "%f %n", &ed->interval, &n) <= 0) {
259       NORM_ERR("missing execi interval: ${execi* <interval> command}");
260       delete ed;
261       ed = nullptr;
262       return;
263     }
264 
265     /* set cmd to everything after the interval */
266     cmd = strndup(arg + n, text_buffer_size.get(*state));
267     orig_cmd = const_cast<char *>(cmd);
268   }
269 
270   /* parse any special options for the graphical exec types */
271   if ((execflag & EF_BAR) != 0u) {
272     cmd = scan_bar(obj, cmd, 100);
273 #ifdef BUILD_X11
274   } else if ((execflag & EF_GAUGE) != 0u) {
275     cmd = scan_gauge(obj, cmd, 100);
276   } else if ((execflag & EF_GRAPH) != 0u) {
277     cmd = scan_graph(obj, cmd, 100);
278     if (cmd == nullptr) {
279       NORM_ERR("error parsing arguments to execgraph object");
280     }
281 #endif /* BUILD_X11 */
282   }
283 
284   /* finally, store the resulting command, or an empty string if something went
285    * wrong */
286   if ((execflag & EF_EXEC) != 0u) {
287     obj->data.s =
288         strndup(cmd != nullptr ? cmd : "", text_buffer_size.get(*state));
289   } else if ((execflag & EF_EXECI) != 0u) {
290     ed->cmd = strndup(cmd != nullptr ? cmd : "", text_buffer_size.get(*state));
291     obj->data.opaque = ed;
292   }
293   free_and_zero(orig_cmd);
294 }
295 
296 /**
297  * Register an exec_cb object using the command that we have parsed
298  *
299  * @param[out] obj stores the callback handle
300  */
register_exec(struct text_object * obj)301 void register_exec(struct text_object *obj) {
302   if ((obj->data.s != nullptr) && (obj->data.s[0] != 0)) {
303     obj->exec_handle = new conky::callback_handle<exec_cb>(
304         conky::register_cb<exec_cb>(1, true, obj->data.s));
305   } else {
306     DBGP("unable to register exec callback");
307   }
308 }
309 
310 /**
311  * Register an exec_cb object using the command that we have parsed.
312  *
313  * This version takes care of execi intervals. Note that we depend on
314  * obj->thread, so be sure to run this function *after* setting obj->thread.
315  *
316  * @param[out] obj stores the callback handle
317  */
register_execi(struct text_object * obj)318 void register_execi(struct text_object *obj) {
319   auto *ed = static_cast<struct execi_data *>(obj->data.opaque);
320 
321   if ((ed != nullptr) && (ed->cmd != nullptr) && (ed->cmd[0] != 0)) {
322     uint32_t period =
323         std::max(lround(ed->interval / active_update_interval()), 1l);
324     obj->exec_handle = new conky::callback_handle<exec_cb>(
325         conky::register_cb<exec_cb>(period, !obj->thread, ed->cmd));
326   } else {
327     DBGP("unable to register execi callback");
328   }
329 }
330 
331 /**
332  * Get the results of an exec_cb object (command output)
333  *
334  * @param[in] obj holds an exec_handle, assuming one was registered
335  * @param[out] p the string in which we store command output
336  * @param[in] p_max_size the maximum size of p...
337  */
print_exec(struct text_object * obj,char * p,unsigned int p_max_size)338 void print_exec(struct text_object *obj, char *p, unsigned int p_max_size) {
339   if (obj->exec_handle != nullptr) {
340     fill_p((*obj->exec_handle)->get_result_copy().c_str(), obj, p, p_max_size);
341   }
342 }
343 
344 /**
345  * Get the results of a graphical (bar, gauge, graph) exec_cb object
346  *
347  * @param[in] obj hold an exec_handle, assuming one was registered
348  * @return a value between 0.0 and 100.0
349  */
execbarval(struct text_object * obj)350 double execbarval(struct text_object *obj) {
351   if (obj->exec_handle != nullptr) {
352     return get_barnum((*obj->exec_handle)->get_result_copy().c_str());
353   }
354   return 0.0;
355 }
356 
357 /**
358  * Free up any dynamically allocated data
359  *
360  * @param[in] obj holds the data that we need to free up
361  */
free_exec(struct text_object * obj)362 void free_exec(struct text_object *obj) {
363   free_and_zero(obj->data.s);
364   delete obj->exec_handle;
365   obj->exec_handle = nullptr;
366 }
367 
368 /**
369  * Free up any dynamically allocated data, specifically for execi objects
370  *
371  * @param[in] obj holds the data that we need to free up
372  */
free_execi(struct text_object * obj)373 void free_execi(struct text_object *obj) {
374   auto *ed = static_cast<struct execi_data *>(obj->data.opaque);
375 
376   /* if ed is nullptr, there is nothing to do */
377   if (ed == nullptr) { return; }
378 
379   delete obj->exec_handle;
380   obj->exec_handle = nullptr;
381 
382   free_and_zero(ed->cmd);
383   delete ed;
384   ed = nullptr;
385   obj->data.opaque = nullptr;
386 }
387