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