Last active
October 17, 2019 22:28
-
-
Save dbnicholson/15f401935d5b34dd3bac9ea3fc069918 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Build with: | |
* cc -o monitor-bug $(pkg-config --cflags --libs gio-unix-2.0) monitor-bug.c | |
*/ | |
#include <locale.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
#include <err.h> | |
#include <signal.h> | |
#include <glib.h> | |
#include <glib-unix.h> | |
#include <gio/gio.h> | |
#include <gio/gunixoutputstream.h> | |
typedef struct { | |
gboolean running; | |
GOutputStream *log; | |
} MonitorData; | |
static gboolean opt_daemon; | |
static char *opt_log_file; | |
static char *opt_pid_file; | |
static gboolean opt_log_after_fork; | |
static GOptionEntry options[] = { | |
{ "daemon", 'd', 0, G_OPTION_ARG_NONE, &opt_daemon, "Fork when ready", NULL }, | |
{ "log", 'l', 0, G_OPTION_ARG_FILENAME, &opt_log_file, "Send logs here", "PATH" }, | |
{ "pid", 'p', 0, G_OPTION_ARG_FILENAME, &opt_pid_file, "Write child PID here", "PATH" }, | |
{ "log-after-fork", 0, 0, G_OPTION_ARG_NONE, &opt_log_after_fork, "Open log files after fork", NULL }, | |
{ NULL } | |
}; | |
static void write_log (GOutputStream *log, const char *format, ...) __attribute__ ((format(printf, 2, 3))); | |
static void | |
write_log (GOutputStream *log, const char *format, ...) | |
{ | |
g_autoptr(GString) str = g_string_new (NULL); | |
va_list args; | |
va_start (args, format); | |
g_string_vprintf (str, format, args); | |
va_end (args); | |
g_output_stream_write_all (log, str->str, str->len, NULL, NULL, NULL); | |
g_output_stream_flush (log, NULL, NULL); | |
} | |
static void | |
monitor_event (GFileMonitor *monitor, | |
GFile *file, | |
GFile *other, | |
GFileMonitorEvent event, | |
gpointer user_data) | |
{ | |
MonitorData *monitor_data = user_data; | |
write_log (monitor_data->log, "Received event %i\n", event); | |
if (event == G_FILE_MONITOR_EVENT_DELETED) | |
{ | |
write_log (monitor_data->log, "Directory removed, exiting\n"); | |
monitor_data->running = FALSE; | |
} | |
} | |
static void | |
setup_logging (GOutputStream **log) | |
{ | |
g_autoptr(GError) error = NULL; | |
if (opt_log_file) | |
{ | |
/* I'd prefer to use g_file_replace, but that doesn't actually | |
* replace the file until the stream is closed, which isn't | |
* helpful if you want to read the log while the program is | |
* running. | |
*/ | |
g_autoptr(GFile) log_file = g_file_new_for_path (opt_log_file); | |
g_file_delete (log_file, NULL, NULL); | |
*log = G_OUTPUT_STREAM (g_file_create (log_file, | |
G_FILE_CREATE_PRIVATE, | |
NULL, | |
&error)); | |
if (!*log) | |
errx (1, "%s", error->message); | |
} | |
else | |
{ | |
*log = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, | |
FALSE)); | |
} | |
} | |
static gboolean | |
sighandler (gpointer user_data) | |
{ | |
MonitorData *monitor_data = user_data; | |
write_log (monitor_data->log, "Received signal, exiting\n"); | |
monitor_data->running = FALSE; | |
return G_SOURCE_REMOVE; | |
} | |
int | |
main(int argc, char *argv[]) | |
{ | |
g_autoptr(GOptionContext) optcontext = NULL; | |
const char *dirpath; | |
MonitorData monitor_data = { 0, }; | |
g_autoptr(GOutputStream) log = NULL; | |
g_autoptr(GError) error = NULL; | |
g_autoptr(GCancellable) cancellable = NULL; | |
g_autoptr(GFile) dir = NULL; | |
g_autoptr(GFileMonitor) monitor = NULL; | |
setlocale (LC_ALL, ""); | |
g_set_prgname (argv[0]); | |
optcontext = g_option_context_new ("[DIR] - Directory monitoring test"); | |
g_option_context_add_main_entries (optcontext, options, NULL); | |
if (!g_option_context_parse (optcontext, &argc, &argv, &error)) | |
errx (1, "%s", error->message); | |
if (argc > 1) | |
dirpath = argv[1]; | |
else | |
dirpath = "."; | |
if (!opt_log_after_fork) | |
setup_logging (&log); | |
if (opt_daemon) | |
{ | |
pid_t pid = fork (); | |
if (pid == -1) | |
err (1, "fork"); | |
else if (pid > 0) | |
{ | |
/* Parent. Print the child PID and exit */ | |
if (opt_pid_file) | |
{ | |
g_autofree char *pid_msg = g_strdup_printf ("%u\n", pid); | |
g_file_set_contents (opt_pid_file, pid_msg, -1, NULL); | |
} | |
else | |
{ | |
g_print ("%u\n", pid); | |
} | |
return 0; | |
} | |
} | |
if (opt_log_after_fork) | |
setup_logging (&log); | |
monitor_data.log = log; | |
dir = g_file_new_for_path (dirpath); | |
monitor = g_file_monitor_directory (dir, G_FILE_MONITOR_NONE, cancellable, | |
&error); | |
if (!monitor) | |
errx (1, "%s", error->message); | |
g_signal_connect (monitor, "changed", G_CALLBACK (monitor_event), | |
&monitor_data); | |
write_log (log, "Monitoring '%s'\n", dirpath); | |
g_unix_signal_add (SIGINT, sighandler, &monitor_data); | |
g_unix_signal_add (SIGTERM, sighandler, &monitor_data); | |
monitor_data.running = TRUE; | |
while (monitor_data.running) | |
g_main_context_iteration (NULL, TRUE); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment