diff --git a/src/utils.c b/src/utils.c
index 025fe1e4261809fbcf92122cc2de51f06b42eb72..55450d3c413676a4c2ebceb65a1f8b82c7d2f5c2 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -523,3 +523,24 @@ pv_divert_stdout_to_stderr (GError **error)
 
   return g_steal_pointer (&original_stdout);
 }
+
+/**
+ * pv_async_signal_safe_error:
+ * @message: A human-readable message
+ * @exit_status: Call `_exit` with this status
+ *
+ * Exit with a fatal error, like g_error(), but async-signal-safe
+ * (see signal-safety(7)).
+ */
+void
+pv_async_signal_safe_error (const char *message,
+                            int exit_status)
+{
+  if (write (2, message, strlen (message)) < 0)
+    {
+      /* Ignore - there's nothing we can do about it anyway - but
+       * suppress -Wunused-result. */
+    }
+
+  _exit (exit_status);
+}
diff --git a/src/utils.h b/src/utils.h
index 021a60dc5833ff38d98e3c6c485dc7257a652b3b..49c065ef3fb386195bf2401745b136272eb10926 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -55,3 +55,6 @@ gboolean pv_boolean_environment (const gchar *name,
                                  gboolean def);
 
 FILE *pv_divert_stdout_to_stderr (GError **error);
+
+void pv_async_signal_safe_error (const char *message,
+                                 int exit_status) G_GNUC_NORETURN;