Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/conmon.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ Specify the Container UUID to use.
**--version**
Print the version and exit.

**--timer-command**
Execute COMMAND every SECONDS. Format: ID:SECONDS:COMMAND. Can be specified multiple times.

**--timer-command-argument**
Add an argument to timer-command with ID. Format: ID:ARGUMENT. Can be specified multiple times.

This option allows adding command-line arguments to a specific timer-command by ID.
Arguments are added in the order they are specified.

## SEE ALSO
podman(1), buildah(1), cri-o(1), crun(8), runc(8)

Expand Down
119 changes: 119 additions & 0 deletions src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#include <glib-unix.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#ifdef __linux__
#include <linux/limits.h>
#endif
Expand Down Expand Up @@ -57,6 +60,9 @@ char *opt_sdnotify_socket = NULL;
gboolean opt_full_attach_path = FALSE;
char *opt_seccomp_notify_socket = NULL;
char *opt_seccomp_notify_plugins = NULL;
gchar **opt_timer_command = NULL;
gchar **opt_timer_command_argument = NULL;
GPtrArray *timer_command_entries = NULL;
GOptionEntry opt_entries[] = {
{"api-version", 0, 0, G_OPTION_ARG_NONE, &opt_api_version, "Conmon API version to use", NULL},
{"bundle", 'b', 0, G_OPTION_ARG_STRING, &opt_bundle_path, "Location of the OCI Bundle path", NULL},
Expand Down Expand Up @@ -117,6 +123,10 @@ GOptionEntry opt_entries[] = {
"Path to the socket where the seccomp notification fd is received", NULL},
{"seccomp-notify-plugins", 0, 0, G_OPTION_ARG_STRING, &opt_seccomp_notify_plugins,
"Plugins to use for managing the seccomp notifications", NULL},
{"timer-command", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_timer_command,
"Execute COMMAND every SECONDS. Format: ID:SECONDS:COMMAND. Can be specified multiple times", NULL},
{"timer-command-argument", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_timer_command_argument,
"Add an argument to timer-command with ID. Format: ID:ARGUMENT. Can be specified multiple times", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}};


Expand Down Expand Up @@ -205,4 +215,113 @@ void process_cli()
if (opt_no_container_partial_message && !logging_is_journald_enabled()) {
nwarnf("--no-container-partial-message has no effect without journald log driver");
}

/* Parse timer-command entries with ID-based arguments */
if (opt_timer_command || opt_timer_command_argument) {
timer_command_entries = g_ptr_array_new_with_free_func(g_free);

/* Parse timer-command commands first */
if (opt_timer_command) {
for (int i = 0; opt_timer_command[i] != NULL; i++) {
char *entry = g_strdup(opt_timer_command[i]);
char *first_colon = strchr(entry, ':');
if (!first_colon) {
nexitf("Invalid timer-command format '%s'. Expected ID:SECONDS:COMMAND", opt_timer_command[i]);
}

*first_colon = '\0';
char *second_colon = strchr(first_colon + 1, ':');
if (!second_colon) {
nexitf("Invalid timer-command format '%s'. Expected ID:SECONDS:COMMAND", opt_timer_command[i]);
}

*second_colon = '\0';

char *id_str = entry;
char *interval_str = first_colon + 1;
char *command = second_colon + 1;

/* Parse ID */
char *endptr;
errno = 0;
long id_long = strtol(id_str, &endptr, 10);
if (errno != 0 || endptr == id_str || *endptr != '\0' || id_long < 0 || id_long > INT_MAX) {
nexitf("Invalid timer-command ID '%s' in '%s'", id_str, opt_timer_command[i]);
}
int id = (int)id_long;

/* Require incremental IDs starting from 0 */
int expected_id = i;
if (id != expected_id) {
nexitf("Timer-command IDs must be incremental starting from 0. Expected %d, got %d", expected_id,
id);
}

/* Parse interval */
errno = 0;
long interval_long = strtol(interval_str, &endptr, 10);
if (errno != 0 || endptr == interval_str || *endptr != '\0' || interval_long <= 0
|| interval_long > INT_MAX) {
nexitf("Invalid timer-command interval '%s' in '%s'", interval_str, opt_timer_command[i]);
}
int interval = (int)interval_long;

if (*command == '\0') {
nexitf("Empty timer-command command in '%s'", opt_timer_command[i]);
}

timer_command_entry_t *cc_entry = g_malloc(sizeof(timer_command_entry_t));
cc_entry->id = id;
cc_entry->interval = interval;
cc_entry->args = g_malloc(sizeof(gchar *) * 2);
cc_entry->args[0] = g_strdup(command);
cc_entry->args[1] = NULL;

g_ptr_array_add(timer_command_entries, cc_entry);

ndebugf("Added timer-command: ID=%d, %d seconds, command: %s", id, interval, command);
g_free(entry);
}
}

if (opt_timer_command_argument) {
for (int i = 0; opt_timer_command_argument[i] != NULL; i++) {
char *entry = g_strdup(opt_timer_command_argument[i]);
char *colon = strchr(entry, ':');
if (!colon) {
nexitf("Invalid timer-command-argument format '%s'. Expected ID:ARGUMENT",
opt_timer_command_argument[i]);
}

*colon = '\0';
char *id_str = entry;
char *argument = colon + 1;

/* Parse ID */
char *endptr;
errno = 0;
long id_long = strtol(id_str, &endptr, 10);
if (errno != 0 || endptr == id_str || *endptr != '\0' || id_long < 0 || id_long > INT_MAX) {
nexitf("Invalid timer-command ID '%s' in argument '%s'", id_str, opt_timer_command_argument[i]);
}
int id = (int)id_long;

if (id < 0 || id >= (int)timer_command_entries->len) {
nexitf("Timer-command ID %d not found for argument '%s'", id, argument);
}
timer_command_entry_t *target_entry = g_ptr_array_index(timer_command_entries, id);

int arg_count = 0;
while (target_entry->args[arg_count])
arg_count++;

target_entry->args = g_realloc(target_entry->args, sizeof(gchar *) * (arg_count + 2));
target_entry->args[arg_count] = g_strdup(argument);
target_entry->args[arg_count + 1] = NULL;

ndebugf("Added argument '%s' to timer-command ID %d", argument, id);
g_free(entry);
}
}
}
}
11 changes: 11 additions & 0 deletions src/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <glib.h> /* gboolean and GOptionEntry */
#include <stdint.h> /* int64_t */
#include <time.h> /* time_t */

extern gboolean opt_version;
extern gboolean opt_terminal;
Expand Down Expand Up @@ -49,6 +50,16 @@ extern char *opt_seccomp_notify_socket;
extern char *opt_seccomp_notify_plugins;
extern GOptionEntry opt_entries[];
extern gboolean opt_full_attach_path;
extern gchar **opt_timer_command;
extern gchar **opt_timer_command_argument;

typedef struct {
int id;
int interval;
gchar **args;
} timer_command_entry_t;

extern GPtrArray *timer_command_entries;

int initialize_cli(int argc, char *argv[]);
void process_cli();
Expand Down
20 changes: 20 additions & 0 deletions src/conmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ static void disconnect_std_streams(int dev_null_r, int dev_null_w)
pexit("Failed to dup over stderr");
}


static gboolean timer_command_timer_cb(gpointer user_data)
{
timer_command_entry_t *entry = (timer_command_entry_t *)user_data;

ndebugf("Executing timer-command: %s", entry->args[0]);
execute_command_detached(entry->args, 0);

return G_SOURCE_CONTINUE; // Keep repeating
}

#define DEFAULT_UMASK 0022

int main(int argc, char *argv[])
Expand Down Expand Up @@ -423,6 +434,15 @@ int main(int argc, char *argv[])
g_timeout_add_seconds(opt_timeout, timeout_cb, NULL);
}

/* Add individual timers for each timer-command entry */
if (timer_command_entries && timer_command_entries->len > 0) {
for (guint i = 0; i < timer_command_entries->len; i++) {
timer_command_entry_t *entry = g_ptr_array_index(timer_command_entries, i);
g_timeout_add_seconds(entry->interval, timer_command_timer_cb, entry);
ndebugf("Timer-command timer set up: %d seconds, command: %s", entry->interval, entry->args[0]);
}
}

if (data.exit_status_cache) {
GHashTableIter iter;
gpointer key, value;
Expand Down
109 changes: 77 additions & 32 deletions src/ctr_exit.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
#include "oom.h"

#include <errno.h>
#include <fcntl.h>
#include <glib.h>
#include <glib-unix.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

volatile sig_atomic_t container_pid = -1;
Expand Down Expand Up @@ -152,53 +154,105 @@ void container_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointe
g_main_loop_quit(main_loop);
}

void do_exit_command()
/*
* Execute a command with double fork to completely detach the process.
* Used for timer-command commands that should run independently of conmon.
* args[0] must be the command path and args must be NULL-terminated.
*/
void execute_command_detached(gchar **args, int delay)
{
if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
_pexit("Failed to reset signal for SIGCHLD");
pid_t first_fork = fork();
if (first_fork < 0) {
nwarnf("Failed to fork for command: %s", args[0]);
return;
}

if (first_fork) {
int status;
waitpid(first_fork, &status, 0);
return;
}

pid_t second_fork = fork();
if (second_fork < 0) {
_exit(1);
} else if (second_fork == 0) {
close_all_fds_ge_than(3);

int null_fd = open("/dev/null", O_RDWR);
if (null_fd >= 0) {
dup2(null_fd, STDIN_FILENO);
dup2(null_fd, STDOUT_FILENO);
dup2(null_fd, STDERR_FILENO);
if (null_fd > 2) {
close(null_fd);
}
}

setsid();

execute_command(args, delay, false);
_exit(EXIT_FAILURE);
} else {
_exit(0);
}
}

/*
* Close everything except stdin, stdout and stderr.
*/
/*
* Execute a command with single fork.
* If do_wait is true, wait for completion (used by exit commands).
* If do_wait is false, don't wait (used by detached commands after double fork).
* args[0] must be the command path and args must be NULL-terminated.
*/
void execute_command(gchar **args, int delay, gboolean do_wait)
{
close_all_fds_ge_than(3);

/*
* We don't want the exit command to be reaped by the parent conmon
* as that would prevent double-fork from doing its job.
* Unfortunately, that also means that any new subchildren from
* still running processes could also get lost
*/
if (set_subreaper(false) != 0) {
nwarn("Failed to disable self subreaper attribute - might wait for indirect children a long time");
}

pid_t exit_pid = fork();
if (exit_pid < 0) {
pid_t child_pid = fork();
if (child_pid < 0) {
_pexit("Failed to fork");
}

if (exit_pid) {
if (child_pid) {
if (!do_wait) {
return;
}
int ret, exit_status = 0;

/*
* Make sure to cleanup any zombie process that the container runtime
* could have left around.
*/
do {
int tmp;

exit_status = 0;
ret = waitpid(-1, &tmp, 0);
if (ret == exit_pid)
if (ret == child_pid)
exit_status = get_exit_status(tmp);
} while ((ret < 0 && errno == EINTR) || ret > 0);

if (exit_status)
_exit(exit_status);

return;
}
if (delay > 0) {
ndebugf("Sleeping for %d seconds before executing command", delay);
sleep(delay);
}

reset_oom_adjust();

execv(args[0], args);

_exit(EXIT_FAILURE);
}

void do_exit_command()
{
if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
_pexit("Failed to reset signal for SIGCHLD");
}

/* Count the additional args, if any. */
size_t n_args = 0;
Expand All @@ -214,19 +268,10 @@ void do_exit_command()
args[n_args + 1] = opt_exit_args[n_args];
args[n_args + 1] = NULL;

if (opt_exit_delay) {
ndebugf("Sleeping for %d seconds before executing exit command", opt_exit_delay);
sleep(opt_exit_delay);
}

reset_oom_adjust();

execv(opt_exit_command, args);

/* Should not happen, but better be safe. */
_exit(EXIT_FAILURE);
execute_command(args, opt_exit_delay, true);
}


void reap_children()
{
/* We need to reap any zombies (from an OCI runtime that errored) before
Expand Down
2 changes: 2 additions & 0 deletions src/ctr_exit.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ gboolean timeout_cb(G_GNUC_UNUSED gpointer user_data);
int get_exit_status(int status);
void runtime_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data);
void container_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data);
void execute_command_detached(gchar **args, int delay);
void execute_command(gchar **args, int delay, gboolean do_wait);
void do_exit_command();
void reap_children();
void cleanup_socket_dir_symlink();
Expand Down
Loading
Loading