advgetopt 2.0.49
Parse complex command line arguments and configuration files in C++.
hide_warnings.cpp
Go to the documentation of this file.
1// Copyright (c) 2006-2025 Made to Order Software Corp. All Rights Reserved
2//
3// https://snapwebsites.org/project/advgetopt
4// contact@m2osw.com
5//
6// This program is free software; you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation; either version 2 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License along
17// with this program; if not, write to the Free Software Foundation, Inc.,
18// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
57#ifndef _GNU_SOURCE
58#define _GNU_SOURCE
59#endif
60
61// C++
62//
63#include <iostream>
64
65
66// C
67//
68#include <unistd.h>
69#include <stdlib.h>
70#include <stdio.h>
71#include <string.h>
72#include <fcntl.h>
73#include <limits.h>
74#include <errno.h>
75#include <regex.h>
76#include <poll.h>
77
78
79
80char const * const g_version = "1.0";
81char const * const g_default_regex = "gtk-warning|gtk-critical|glib-gobject-warning|^$";
82
83char const * g_progname = NULL;
84char const * g_regex = NULL;
87
88#define IN_OUT_BUFSIZ (64 * 1024)
89struct io_buf
90{
91 size_t f_pos = 0;
92 char f_buf[IN_OUT_BUFSIZ + 1] = { 0 };
93};
96
97
98void usage()
99{
100 std::cout << "Usage: " << g_progname << " [--opts] command [cmd-opts]" << std::endl
101 << "Where --opts is one or more of:" << std::endl
102 << " --help | -h print out this help screen" << std::endl
103 << " --version | -V print out the version of " << g_progname << std::endl
104 << " --regex | -r 'regex' regex of messages to hide" << std::endl
105 << " --case | -c make the regex case sensitive" << std::endl
106 << " --out also filter stdout" << std::endl
107 << " -- end list of " << g_progname << " options\n" << std::endl
108 << "And where command and [cmd-opts] is the command to execute and its options." << std::endl;
109 exit(0);
110}
111
112
113
114void output_data(FILE * out, regex_t const * regex, char * str, size_t len)
115{
116 int saved_errno, r;
117 size_t sz;
118 char save_eol;
119
120 if(regex != NULL)
121 {
122 /* run the regex without the "\n" included */
123 sz = len > 0 && str[len - 1] == '\n' ? len - 1 : len;
124 save_eol = str[sz];
125 str[sz] = '\0';
126 r = regexec(regex, str, 0, NULL, 0);
127 str[sz] = save_eol;
128 if(r == 0)
129 {
130 /* the pattern matched, the user does not want to see that one */
131 return;
132 }
133 }
134
135 sz = fwrite(str, len, 1, out);
136 if(sz != 1)
137 {
138 saved_errno = errno;
139 std::cerr << std::endl
140 << g_progname << ":error: write() to stdout/stderr failed: "
141 << strerror(saved_errno)
142 << ". ("
143 << sz
144 << ")";
145 exit(1);
146 }
147}
148
149
150void read_pipe(int pipe, FILE * out, regex_t const * regex, struct io_buf * io)
151{
152 ssize_t sz;
153
154 for(;;)
155 {
156 /* read some data */
157 sz = read(pipe, io->f_buf + io->f_pos, IN_OUT_BUFSIZ - io->f_pos);
158 if(sz <= 0)
159 {
160 if(sz < 0
161 && errno != EAGAIN && errno != EWOULDBLOCK)
162 {
163 std::cerr << g_progname
164 << ":error: read() of stdout pipe failed."
165 << std::endl;
166 exit(1);
167 }
168 return;
169 }
170
171 /* got some data, search for a "\n" */
172 for(; sz > 0; --sz)
173 {
174 if(io->f_buf[io->f_pos] == '\n')
175 {
176 /* write that to the output, including the "\n" */
177 ++io->f_pos;
178 output_data(out, regex, io->f_buf, io->f_pos);
179 memmove(io->f_buf, io->f_buf + io->f_pos, sz);
180 io->f_pos = 0;
181 }
182 else
183 {
184 ++io->f_pos;
185 }
186 }
187 if(io->f_pos >= IN_OUT_BUFSIZ)
188 {
189 /* our buffer is full, we must empty it... (but it should
190 * be rare that a normal process outputs a string of over 64Kb
191 * without at least one "\n" character!)
192 */
193 output_data(out, regex, io->f_buf, IN_OUT_BUFSIZ);
194 io->f_pos = 0;
195 }
196 }
197}
198
199
200int main(int argc, char * argv[], char * envp[])
201{
202 int i, pipe_out[2], pipe_err[2], child_pid, saved_errno;
203 size_t j, len, cmd_len;
204 char * path, * p, * e, * n;
205 struct pollfd fds[2];
206 nfds_t count;
207 regex_t regex;
208
209 /* get the basename from argv[0] */
210 g_progname = strrchr(argv[0], '/');
211 if(g_progname == NULL)
212 {
213 g_progname = argv[0];
214 }
215 else
216 {
217 /* skip the '/' */
218 ++g_progname;
219 }
220
221 /* if there are some parameters that start with '-' or '--'
222 * before a parameter without such, then these are command line
223 * options to hide-warnings
224 */
225
227
228 for(i = 1; i < argc; ++i)
229 {
230 if(argv[i][0] == '-')
231 {
232 /* long option? */
233 if(argv[i][1] == '-')
234 {
235 if(argv[i][2] == '\0')
236 {
237 /* we found a "--" */
238 break;
239 }
240
241 if(strcmp(argv[i] + 2, "help") == 0)
242 {
243 usage();
244 /*NOTREACHED*/
245 }
246 if(strcmp(argv[i] + 2, "version") == 0)
247 {
248 std::cout << g_version << std::endl;
249 exit(0);
250 /*NOTREACHED*/
251 }
252 if(strcmp(argv[i] + 2, "regex") == 0)
253 {
254 ++i;
255 if(i >= argc)
256 {
257 std::cerr << g_progname
258 << ":error: --regex must be followed by a regular expression."
259 << std::endl;
260 exit(1);
261 }
262 g_regex = argv[i];
263 }
264 else if(strncmp(argv[i] + 2, "regex=", 6) == 0)
265 {
266 g_regex = argv[i] + 8;
267 }
268 else if(strcmp(argv[i] + 2, "case") == 0)
269 {
271 }
272 else if(strcmp(argv[i] + 2, "out") == 0)
273 {
274 g_filter_stdout = 1;
275 }
276 }
277 else
278 {
279 len = strlen(argv[i]);
280 for(j = 1; j < len; ++j)
281 {
282 switch(argv[i][j])
283 {
284 case 'c':
286 break;
287
288 case 'h':
289 usage();
290 /*NOTREACHED*/
291 break;
292
293 case 'r':
294 ++i;
295 if(i >= argc)
296 {
297 std::cerr << g_progname
298 << ":error: --regex must be followed by a regular expression."
299 << std::endl;
300 exit(1);
301 }
302 g_regex = argv[i];
303 break;
304
305 case 'V':
306 std::cout << g_version << std::endl;
307 exit(0);
308 /*NOTREACHED*/
309 break;
310
311 }
312 }
313 }
314 }
315 else
316 {
317 /* i points to the command we want to run now */
318 break;
319 }
320 }
321 if(i >= argc)
322 {
323 std::cerr << g_progname
324 << ":error: no command specified."
325 << std::endl;
326 exit(1);
327 }
328
329 /* the next parameter (starting at 'i') is the command and the following
330 * ones are its parameters
331 */
332
333
334 /* we want to redirect the child I/O to ourselves so we create a couple
335 * of pipes to replace stdout and stderr
336 */
337 if(pipe2(pipe_out, O_NONBLOCK) != 0)
338 {
339 std::cerr << g_progname
340 << ":error: could not create pipe to replace stdout."
341 << std::endl;
342 exit(1);
343 }
344 if(pipe2(pipe_err, O_NONBLOCK) != 0)
345 {
346 std::cerr << g_progname
347 << ":error: could not create pipe to replace stderr."
348 << std::endl;
349 exit(1);
350 }
351
352 child_pid = fork();
353 if(child_pid <= 0)
354 {
355 int fork_errno(errno);
356 close(pipe_out[1]);
357 close(pipe_err[1]);
358
359 if(child_pid == 0)
360 {
361 /* we are the parent and we got a child, duplicate its output
362 * except for lines that match the regex
363 */
364 regcomp(&regex, g_regex, REG_EXTENDED | (g_case_sensitive == 0 ? REG_ICASE : 0) | REG_NOSUB);
365 while(pipe_out[0] != -1 || pipe_err[0] != -1)
366 {
367 memset(&fds, 0, sizeof(fds));
368 fds[0].fd = pipe_out[0];
369 fds[0].events = POLLIN | POLLPRI | POLLRDHUP;
370 fds[0].revents = 0;
371 fds[1].fd = pipe_err[0];
372 fds[1].events = POLLIN | POLLPRI | POLLRDHUP;
373 fds[1].revents = 0;
374 count = pipe_out[0] == -1 || pipe_err[0] == -1 ? 1 : 2;
375 if(poll(fds + (pipe_out[0] == -1 ? 1 : 0), count, -1) < 0)
376 {
377 int const err(errno);
378 std::cerr << g_progname
379 << ":error: poll() returned with -1: "
380 << err
381 << ", "
382 << strerror(err)
383 << "."
384 << std::endl;
385 exit(1);
386 }
387 if((fds[0].revents & (POLLIN | POLLPRI)) != 0)
388 {
389 read_pipe(pipe_out[0], stdout, g_filter_stdout == 0 ? NULL : &regex, &g_buf_out);
390 }
391 if((fds[1].revents & (POLLIN | POLLPRI)) != 0)
392 {
393 read_pipe(pipe_err[0], stderr, &regex, &g_buf_err);
394 }
395 if((fds[0].revents & (POLLHUP | POLLRDHUP)) != 0)
396 {
397 close(pipe_out[0]);
398 pipe_out[0] = -1;
399 }
400 if((fds[1].revents & (POLLHUP | POLLRDHUP)) != 0)
401 {
402 close(pipe_err[0]);
403 pipe_err[0] = -1;
404 }
405 }
406 exit(0);
407 }
408 else
409 {
410 std::cerr << g_progname
411 << ":error: fork() failed: "
412 << fork_errno
413 << ", "
414 << strerror(fork_errno)
415 << "."
416 << std::endl;
417 exit(1);
418 }
419 }
420
421 /* here we are the child */
422
423 /* child does not need the readable side of the pipes */
424 close(pipe_out[0]);
425 close(pipe_err[0]);
426
427 /* redirect stdout/stderr to corresponding pipe */
428 dup2(pipe_out[1], 0);
429 dup2(pipe_err[1], 2);
430
431 if(strchr(argv[i], '/') == nullptr)
432 {
433 /* the command will often be written as is, without a path
434 * so here we first check whether we can find the command
435 *
436 * also, not prepending one of the $PATH paths could be a security
437 * problem since we'd end up using "./<command>" which is not
438 * valid by default...
439 */
440 path = getenv("PATH");
441 if(path == nullptr)
442 {
443 path = strdup("/usr/bin");
444 }
445 else
446 {
447 /* make a copy so that way we can change the ':' in '\0' */
448 path = strdup(path);
449 }
450
451 cmd_len = strlen(argv[i]);
452 for(p = path; *p != '\0'; )
453 {
454 for(e = p; *e != '\0' && *e != ':'; ++e);
455 n = *e == ':' ? e + 1 : e;
456 *e = '\0';
457 len = strlen(p) + 1 + cmd_len + 1; // path + '/' + command + '\0'
458 e = new char[len];
459 snprintf(e, len, "%s/%s", p, argv[i]); // we use snprintf() because 'e' is saved in argv[i] and then we call execve()
460 if(access(e, F_OK) == 0)
461 {
462 if(access(e, R_OK | X_OK) == 0)
463 {
464 /* we found the one we want */
465 argv[i] = e;
466 break;
467 }
468 std::cerr << g_progname
469 << ":error: "
470 << e
471 << " is not an executable."
472 << std::endl;
473 exit(1);
474 }
475 delete [] e;
476 p = n;
477 }
478 }
479
480 /* start command */
481 execve(argv[i], argv + i, envp);
482 saved_errno = errno;
483
484 /* we reach here if execve() cannot start 'command' */
485 std::cerr << g_progname
486 << ":error: execve() failed: "
487 << strerror(saved_errno)
488 << "."
489 << std::endl
490 << g_progname
491 << ":error: Command: "
492 << argv[i];
493 for(++i; i < argc; ++i)
494 {
495 std::cerr << " " << argv[i];
496 }
497 std::cerr << std::endl;
498
499 exit(1);
500}
501
502// vim: ts=4 sw=4 et
int g_filter_stdout
void usage()
int g_case_sensitive
void read_pipe(int pipe, FILE *out, regex_t const *regex, struct io_buf *io)
char const *const g_default_regex
char const *const g_version
struct io_buf g_buf_err
#define IN_OUT_BUFSIZ
void output_data(FILE *out, regex_t const *regex, char *str, size_t len)
int main(int argc, char *argv[], char *envp[])
struct io_buf g_buf_out
char const * g_progname
char const * g_regex
size_t f_pos
char f_buf[IN_OUT_BUFSIZ+1]

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.