Skip to content

Instantly share code, notes, and snippets.

@dbnicholson
Last active October 17, 2019 22:28
Show Gist options
  • Save dbnicholson/15f401935d5b34dd3bac9ea3fc069918 to your computer and use it in GitHub Desktop.
Save dbnicholson/15f401935d5b34dd3bac9ea3fc069918 to your computer and use it in GitHub Desktop.
/* 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