[PATCH 2/2] Add support for different power methods to suspend

Alberto Milone alberto.milone at canonical.com
Tue Jul 15 14:46:54 UTC 2014


pm-utils is now deprecated and, as a result, we should rely on
either Logind (where available) or on sysfs.

While autodetection defaults to either Logind or sysfs (preferring
The former), it is also possible to pass the --pm-method parameter
to specify one of the following methods:

logind, sysfs, pm-utils

Note: quirks are only available when using pm-utils

This makes a dependency on GLib necessary (only to avoid using
the C Dbus bindings).

Similar code should be written to handle S4.

The new functions in fwts_pipeio.c and fwts_stringextras.c come
from Logind's source code.
---
 src/acpi/s3/s3.c                    | 526 +++++++++++++++++++++++++++++++++---
 src/lib/include/fwts_pipeio.h       |   6 +
 src/lib/include/fwts_stringextras.h |   1 +
 src/lib/src/fwts_pipeio.c           | 108 ++++++++
 src/lib/src/fwts_stringextras.c     |  28 ++
 5 files changed, 630 insertions(+), 39 deletions(-)

diff --git a/src/acpi/s3/s3.c b/src/acpi/s3/s3.c
index e5b6ef1..6d6d63d 100644
--- a/src/acpi/s3/s3.c
+++ b/src/acpi/s3/s3.c
@@ -28,9 +28,63 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <time.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+enum pm_methods
+{
+	logind,
+	pm_utils,
+	sysfs,
+	undefined
+};
+
+typedef struct
+{
+	fwts_framework *fw;
+	time_t t_start;
+	time_t t_end;
+	GDBusProxy *logind_proxy;
+	GDBusConnection *logind_connection;
+	GMainLoop *gmainloop;
+} fwts_vars;
+
+static inline void free_fwts_vars(void *vars)
+{
+
+	fwts_vars *var = *(void**)vars;
+
+	if (var) {
+		if (var->logind_proxy) {
+			g_object_unref(var->logind_proxy);
+			var->logind_proxy = NULL;
+		}
+		if (var->logind_connection) {
+			g_object_unref(var->logind_connection);
+			var->logind_connection = NULL;
+		}
+		if (var->gmainloop) {
+			g_main_loop_unref(var->gmainloop);
+			var->gmainloop = NULL;
+		}
+	}
+	free(var);
+	var = NULL;
+}
+
+static inline void freep(void *p)
+{
+	free(*(void**) p);
+}
+
+#define _cleanup_free_ __attribute__((cleanup(freep)))
+#define _cleanup_free_fw_ __attribute__((cleanup(free_fwts_vars)))
+
+#define PM_SUSPEND_LOGIND			"Suspend"
+#define PM_SUSPEND_HYBRID_LOGIND	"HybridSleep"
+#define PM_SUSPEND_PMUTILS			"pm-suspend"
+#define PM_SUSPEND_HYBRID_PMUTILS	"pm-suspend-hybrid"
 
-#define PM_SUSPEND 		"pm-suspend"
-#define PM_SUSPEND_HYBRID 	"pm-suspend-hybrid"
 #define FWTS_SUSPEND		"FWTS_SUSPEND"
 #define FWTS_RESUME		"FWTS_RESUME"
 
@@ -46,6 +100,8 @@ static bool s3_min_max_delay = false;
 static float s3_suspend_time = 15.0;	/* Maximum allowed suspend time */
 static float s3_resume_time = 15.0;	/* Maximum allowed resume time */
 static bool s3_hybrid = false;
+static enum pm_methods pm_method = undefined; /* Default pm-method to use to suspend */
+
 
 static int s3_init(fwts_framework *fw)
 {
@@ -60,6 +116,361 @@ static int s3_init(fwts_framework *fw)
 	return FWTS_OK;
 }
 
+/* Initialise the Dbus proxy for Logind */
+static int logind_init_proxy(fwts_vars *fwts_settings)
+{
+	int status = 0;
+
+	if (fwts_settings->logind_connection == NULL)
+		fwts_settings->logind_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL,  NULL);
+
+	if (fwts_settings->logind_connection == NULL) {
+		status = 1;
+		fwts_log_error(fwts_settings->fw, "Cannot establish a connection to Dbus\n");
+		goto out;
+	}
+
+	if (fwts_settings->logind_proxy == NULL) {
+		fwts_settings->logind_proxy = g_dbus_proxy_new_sync(fwts_settings->logind_connection,
+											 G_DBUS_PROXY_FLAGS_NONE,
+											 NULL, "org.freedesktop.login1",
+											 "/org/freedesktop/login1",
+											 "org.freedesktop.login1.Manager",
+											 NULL, NULL);
+	}
+
+	if (fwts_settings->logind_proxy == NULL) {
+		status = 1;
+		fwts_log_error(fwts_settings->fw, "Cannot establish a connection to login1.Manager\n");
+		goto out;
+	}
+
+out:
+	return status;
+}
+
+/* Callback to handle suspend and resume events */
+static void logind_on_suspend_signal(
+	GDBusConnection *connection,
+	const gchar *sender_name,
+	const gchar *object_path,
+	const gchar *interface_name,
+	const gchar *signal_name,
+	GVariant *parameters,
+	gpointer user_data)
+{
+	gboolean status;
+	fwts_vars *fwts_settings = (fwts_vars *)user_data;
+
+	/* Prevent -Werror=unused-parameter from complaining */
+	FWTS_UNUSED(connection);
+	FWTS_UNUSED(sender_name);
+	FWTS_UNUSED(object_path);
+	FWTS_UNUSED(interface_name);
+	FWTS_UNUSED(signal_name);
+
+	if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE ("(b)"))) {
+		fwts_log_error(fwts_settings->fw, "Suspend type %s\n", g_variant_get_type_string (parameters));
+		return;
+	}
+	else {
+		g_variant_get(parameters, "(b)", &status);
+		fwts_log_info(fwts_settings->fw, "Suspend status: %s\n", status ? "true" : "false");
+
+		if (status) {
+			time(&(fwts_settings->t_start));
+			(void)fwts_klog_write(fwts_settings->fw, "Starting fwts suspend\n");
+			(void)fwts_klog_write(fwts_settings->fw, FWTS_SUSPEND "\n");
+		}
+		else {
+			time(&(fwts_settings->t_end));
+			(void)fwts_klog_write(fwts_settings->fw, FWTS_RESUME "\n");
+			(void)fwts_klog_write(fwts_settings->fw, "Finished fwts resume\n");
+			/* Let's give the system some time to get back from S3
+			 * or Logind will refuse to suspend and shoot both events
+			 * without doing anything
+			 */
+			if (s3_min_delay < 3)
+				sleep(3);
+			g_main_loop_quit(fwts_settings->gmainloop);
+		}
+	}
+}
+
+/* Generic function to test supported Logind actions that reply
+ * with a string
+ */
+static bool logind_can_do_action(fwts_vars *fwts_settings, gchar *action)
+{
+	GVariant *reply;
+	GError *error = NULL;
+	bool status = false;
+	gchar *response;
+
+	if (logind_init_proxy(fwts_settings) != 0)
+		return false;
+
+	reply = g_dbus_proxy_call_sync(fwts_settings->logind_proxy,
+									action,
+									NULL,
+									G_DBUS_CALL_FLAGS_NONE,
+									-1,
+									NULL,
+									&error);
+
+	if (reply != NULL) {
+		if (!g_variant_is_of_type(reply, G_VARIANT_TYPE ("(s)"))) {
+			fwts_log_error(fwts_settings->fw, "Unexpected response to %s action: %s\n", action,
+						   g_variant_get_type_string (reply));
+
+			g_variant_unref(reply);
+			return status;
+		}
+
+		g_variant_get(reply, "(&s)", &response);
+		fwts_log_info(fwts_settings->fw, "Response to %s is %s\n", action, response);
+
+		if (strcmp(response, "challenge") == 0) {
+			fwts_log_error(fwts_settings->fw, "%s action available only after authorisation\n", action);
+		}
+		else if (strcmp(response, "yes") == 0) {
+			fwts_log_info(fwts_settings->fw, "User allowed to execute the %s action\n", action);
+			status = true;
+		}
+		else if (strcmp(response, "no") == 0) {
+			fwts_log_error(fwts_settings->fw, "User not allowed to execute the %s action\n", action);
+		}
+		else if (strcmp(response, "na") == 0) {
+			fwts_log_error(fwts_settings->fw, "Hardware doesn't support %s action\n", action);
+		}
+
+		g_variant_unref(reply);
+	}
+	else {
+		fwts_log_error(fwts_settings->fw, "Invalid response from Logind on %s action\n", action);
+		g_error_free(error);
+	}
+
+	return status;
+}
+
+static bool logind_can_suspend(fwts_vars *fwts_settings)
+{
+	return logind_can_do_action(fwts_settings, "CanSuspend");
+}
+
+static bool logind_can_hybrid_suspend(fwts_vars *fwts_settings)
+{
+	return logind_can_do_action(fwts_settings, "CanHybridSleep");
+}
+
+static bool sysfs_can_suspend()
+{
+	return fwts_file_first_line_contains_string("/sys/power/state", "mem");
+}
+
+static bool sysfs_can_hybrid_suspend()
+{
+	bool status;
+
+	status = fwts_file_first_line_contains_string("/sys/power/state", "disk");
+
+	if (!status)
+		return 0;
+
+	return fwts_file_first_line_contains_string("/sys/power/disk", "suspend");
+}
+
+/* Detect the best available power method */
+static enum pm_methods detect_pm_method(fwts_vars *fwts_settings)
+{
+	if (s3_hybrid ? logind_can_hybrid_suspend(fwts_settings) : logind_can_suspend(fwts_settings))
+		return logind;
+	else if (s3_hybrid ? sysfs_can_hybrid_suspend() : sysfs_can_suspend())
+		return sysfs;
+	else
+		return pm_utils;
+}
+
+/* Call Logind to suspend.
+ * action can be either "Suspend" or "HybridSleep"
+ */
+static gboolean logind_do_suspend(gpointer data)
+{
+	GError *error = NULL;
+	GVariant *reply;
+	fwts_vars *fwts_settings = (fwts_vars *)data;
+
+	/* If the loop is not running, return TRUE so as to repeat the operation */
+	if (g_main_loop_is_running (fwts_settings->gmainloop)) {
+
+		gchar *action = s3_hybrid ? PM_SUSPEND_HYBRID_LOGIND : PM_SUSPEND_LOGIND;
+		fwts_log_info(fwts_settings->fw, "Requesting %s action\n", action);
+		reply = g_dbus_proxy_call_sync(fwts_settings->logind_proxy,
+										action,
+										g_variant_new ("(b)",
+													   FALSE),
+										G_DBUS_CALL_FLAGS_NONE,
+										-1,
+										NULL,
+										&error);
+
+		if (reply != NULL) {
+			g_variant_unref(reply);
+		}
+		else {
+			fwts_log_error(fwts_settings->fw, "Error from Logind: %s\n", error->message);
+
+			g_error_free(error);
+			/* return 1; */
+		}
+
+		return FALSE;
+
+	}
+	fwts_log_info(fwts_settings->fw, "Glib loop not ready\n");
+	return TRUE;
+}
+
+/* Start Glib mainloop and listen to suspend/resume events
+ * coming from Logind.
+ * Exit the loop and return the duration after an event.
+ */
+static int logind_wait_for_suspend_resume(fwts_vars *fwts_settings)
+{
+	guint subscription_id = 0;
+	int duration = 0;
+
+	if (logind_init_proxy(fwts_settings) != 0)
+		return 0;
+
+	subscription_id = g_dbus_connection_signal_subscribe (fwts_settings->logind_connection,
+									  "org.freedesktop.login1", /* sender */
+									  "org.freedesktop.login1.Manager",
+									  "PrepareForSleep",
+									  "/org/freedesktop/login1",
+									  NULL, /* arg0 */
+									  G_DBUS_SIGNAL_FLAGS_NONE,
+									  logind_on_suspend_signal,
+									  fwts_settings,
+									  NULL);
+
+
+	fwts_settings->gmainloop = g_main_loop_new(NULL, FALSE);
+	if (fwts_settings->gmainloop) {
+		g_timeout_add(0.1,
+					  logind_do_suspend,
+					  fwts_settings);
+
+		g_main_loop_run(fwts_settings->gmainloop);
+		duration = (int)(fwts_settings->t_end - fwts_settings->t_start);
+
+		/* Optional, as it will be freed together with the struct */
+		g_main_loop_unref(fwts_settings->gmainloop);
+		fwts_settings->gmainloop = NULL;
+	}
+	else {
+		fwts_log_error(fwts_settings->fw, "Failed to start glib mainloop\n");
+	}
+
+	g_dbus_connection_signal_unsubscribe(fwts_settings->logind_connection, subscription_id);
+
+	return duration;
+}
+
+/* Write to a file and report the errno in the log */
+static int write_to_file_with_log(fwts_framework *fw,
+	const gchar *file,
+	const gchar *content)
+{
+	int status;
+	status = fwts_write_string_file(file, content);
+
+	if (status != 0)
+		fwts_log_error(fw, "Writing to %s failed with errno %d\n", file, status);
+
+	return status;
+}
+
+static int sysfs_do_suspend(fwts_vars *fwts_settings)
+{
+	int status;
+
+	if (s3_hybrid) {
+		status = write_to_file_with_log(fwts_settings->fw, "/sys/power/disk", "suspend");
+
+		if (status != 0)
+			return status;
+
+		status = write_to_file_with_log(fwts_settings->fw, "/sys/power/state", "disk");
+	}
+	else {
+		status = write_to_file_with_log(fwts_settings->fw, "/sys/power/state", "mem");
+	}
+
+	return status;
+}
+
+static int wrap_logind_do_suspend(fwts_vars *fwts_settings,
+	int percent,
+	int *duration,
+	char *str)
+{
+	FWTS_UNUSED(str);
+	fwts_progress_message(fwts_settings->fw, percent, "(Suspending)");
+
+	/* This enters a glib mainloop */
+	*duration = logind_wait_for_suspend_resume(fwts_settings);
+	fwts_log_info(fwts_settings->fw, "S3 duration = %d.", *duration);
+	fwts_progress_message(fwts_settings->fw, percent, "(Resumed)");
+
+	return *duration > 0 ? 0 : 1;
+}
+
+static int wrap_sysfs_do_suspend(fwts_vars *fwts_settings,
+	int percent,
+	int *duration,
+	char *str)
+{
+	int status;
+	FWTS_UNUSED(str);
+	fwts_progress_message(fwts_settings->fw, percent, "(Suspending)");
+	time(&(fwts_settings->t_start));
+	(void)fwts_klog_write(fwts_settings->fw, "Starting fwts suspend\n");
+	(void)fwts_klog_write(fwts_settings->fw, FWTS_SUSPEND "\n");
+	status = sysfs_do_suspend(fwts_settings);
+	(void)fwts_klog_write(fwts_settings->fw, FWTS_RESUME "\n");
+	(void)fwts_klog_write(fwts_settings->fw, "Finished fwts resume\n");
+	time(&(fwts_settings->t_end));
+	fwts_progress_message(fwts_settings->fw, percent, "(Resumed)");
+
+	*duration = (int)(fwts_settings->t_end - fwts_settings->t_start);
+
+	return status;
+}
+
+static int wrap_pmutils_do_suspend(fwts_vars *fwts_settings,
+	int percent,
+	int *duration,
+	char *command)
+{
+	int status;
+	fwts_progress_message(fwts_settings->fw, percent, "(Suspending)");
+	time(&(fwts_settings->t_start));
+	(void)fwts_klog_write(fwts_settings->fw, "Starting fwts suspend\n");
+	(void)fwts_klog_write(fwts_settings->fw, FWTS_SUSPEND "\n");
+	(void)fwts_exec(command, &status);
+	(void)fwts_klog_write(fwts_settings->fw, FWTS_RESUME "\n");
+	(void)fwts_klog_write(fwts_settings->fw, "Finished fwts resume\n");
+	time(&(fwts_settings->t_end));
+	fwts_progress_message(fwts_settings->fw, percent, "(Resumed)");
+
+	*duration = (int)(fwts_settings->t_end - fwts_settings->t_start);
+
+	return status;
+}
+
+
 static int s3_do_suspend_resume(fwts_framework *fw,
 	int *hw_errors,
 	int *pm_errors,
@@ -70,54 +481,80 @@ static int s3_do_suspend_resume(fwts_framework *fw,
 	int status;
 	int duration;
 	int differences;
-	time_t t_start;
-	time_t t_end;
-	char *command;
-	char *quirks;
+	_cleanup_free_ char *command = NULL;
+	_cleanup_free_ char *quirks = NULL;
+	_cleanup_free_fw_ fwts_vars * fwts_settings = NULL;
 	char buffer[80];
 
+
+	int (*do_suspend)(fwts_vars *, int, int*, char*);
+
+	fwts_settings = calloc(1, sizeof(fwts_vars));
+	fwts_settings->fw = fw;
+
+
+	if (pm_method == undefined) {
+		/* Autodetection */
+		fwts_log_info(fw, "Detecting the power method.");
+		pm_method = detect_pm_method(fwts_settings);
+	}
+
+	switch (pm_method) {
+		case logind:
+			fwts_log_info(fw, "Using logind as the default power method.");
+			if (logind_init_proxy(fwts_settings) != 0) {
+				fwts_log_error(fw, "Failure to connect to Logind.");
+				return FWTS_ERROR;
+			}
+			do_suspend = &wrap_logind_do_suspend;
+			break;
+		case pm_utils:
+			fwts_log_info(fw, "Using pm-utils as the default power method.");
+			do_suspend = &wrap_pmutils_do_suspend;
+			break;
+		case sysfs:
+			fwts_log_info(fw, "Using sysfs as the default power method.");
+			do_suspend = &wrap_sysfs_do_suspend;
+			break;
+		default:
+			/* This should never happen */
+			fwts_log_info(fw, "Using sysfs as the default power method.");
+			do_suspend = &wrap_sysfs_do_suspend;
+			break;
+	}
+
 	if (s3_device_check)
 		fwts_hwinfo_get(fw, &hwinfo1);
 
 	/* Format up pm-suspend command with optional quirking arguments */
-	if (s3_hybrid) {
-		if ((command = fwts_realloc_strcat(NULL, PM_SUSPEND_HYBRID)) == NULL)
-			return FWTS_OUT_OF_MEMORY;
-	} else {
-		if ((command = fwts_realloc_strcat(NULL, PM_SUSPEND)) == NULL)
-			return FWTS_OUT_OF_MEMORY;
-	}
-
-	if (s3_quirks) {
-		if ((command = fwts_realloc_strcat(command, " ")) == NULL)
-			return FWTS_OUT_OF_MEMORY;
-		if ((quirks = fwts_args_comma_list(s3_quirks)) == NULL) {
-			free(command);
-			return FWTS_OUT_OF_MEMORY;
+	if (pm_method == pm_utils) {
+		if (s3_hybrid) {
+			if ((command = fwts_realloc_strcat(NULL, PM_SUSPEND_HYBRID_PMUTILS)) == NULL)
+				return FWTS_OUT_OF_MEMORY;
+		} else {
+			if ((command = fwts_realloc_strcat(NULL, PM_SUSPEND_PMUTILS)) == NULL)
+				return FWTS_OUT_OF_MEMORY;
 		}
-		if ((command = fwts_realloc_strcat(command, quirks)) == NULL) {
-			free(quirks);
-			return FWTS_OUT_OF_MEMORY;
+
+		/* For now we only support quirks with pm_utils */
+		if (s3_quirks) {
+			if ((command = fwts_realloc_strcat(command, " ")) == NULL)
+				return FWTS_OUT_OF_MEMORY;
+			if ((quirks = fwts_args_comma_list(s3_quirks)) == NULL) {
+				return FWTS_OUT_OF_MEMORY;
+			}
+			if ((command = fwts_realloc_strcat(command, quirks)) == NULL) {
+				return FWTS_OUT_OF_MEMORY;
+			}
 		}
-		free(quirks);
 	}
 
 	fwts_wakealarm_trigger(fw, delay);
 
 	/* Do S3 here */
-	fwts_progress_message(fw, percent, "(Suspending)");
-	time(&t_start);
-	(void)fwts_klog_write(fw, "Starting fwts suspend\n");
-	(void)fwts_klog_write(fw, FWTS_SUSPEND "\n");
-	(void)fwts_exec(command, &status);
-	(void)fwts_klog_write(fw, FWTS_RESUME "\n");
-	(void)fwts_klog_write(fw, "Finished fwts resume\n");
-	time(&t_end);
-	fwts_progress_message(fw, percent, "(Resumed)");
-	free(command);
+	status = do_suspend(fwts_settings, percent, &duration, command);
 
-	duration = (int)(t_end - t_start);
-	fwts_log_info(fw, "pm-suspend returned %d after %d seconds.", status, duration);
+	fwts_log_info(fw, "pm-action returned %d after %d seconds.", status, duration);
 
 	if (s3_device_check) {
 		int i;
@@ -464,9 +901,9 @@ static int s3_options_handler(fwts_framework *fw, int argc, char * const argv[],
 	FWTS_UNUSED(argc);
 	FWTS_UNUSED(argv);
 
-        switch (option_char) {
-        case 0:
-                switch (long_index) {
+		switch (option_char) {
+		case 0:
+				switch (long_index) {
 		case 0:
 			s3_multiple = atoi(optarg);
 			break;
@@ -504,6 +941,16 @@ static int s3_options_handler(fwts_framework *fw, int argc, char * const argv[],
 		case 10:
 			s3_hybrid = true;
 			break;
+		case 11:
+			if (strcmp(optarg, "logind") == 0)
+				pm_method = logind;
+			else if (strcmp(optarg, "pm-utils") == 0)
+				pm_method = pm_utils;
+			else if (strcmp(optarg, "sysfs") == 0)
+				pm_method = sysfs;
+			else
+				return FWTS_ERROR;
+			break;
 		}
 	}
 	return FWTS_OK;
@@ -521,6 +968,7 @@ static fwts_option s3_options[] = {
 	{ "s3-suspend-time",	"", 1, "Maximum expected suspend time in seconds, e.g. --s3-suspend-time=3.5" },
 	{ "s3-resume-time", 	"", 1, "Maximum expected resume time in seconds, e.g. --s3-resume-time=5.1" },
 	{ "s3-hybrid",		"", 0, "Run S3 with hybrid sleep, i.e. saving system states as S4 does." },
+	{ "pm-method",      "", 1, "Select the power method to use. Accepted values are \"logind\", \"pm-utils\", \"sysfs\""},
 	{ NULL, NULL, 0, NULL }
 };
 
diff --git a/src/lib/include/fwts_pipeio.h b/src/lib/include/fwts_pipeio.h
index 2429e0a..bc9b80c 100644
--- a/src/lib/include/fwts_pipeio.h
+++ b/src/lib/include/fwts_pipeio.h
@@ -20,9 +20,11 @@
 #ifndef __FWTS_PIPEIO_H__
 #define __FWTS_PIPEIO_H__
 
+#include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <stdbool.h>
 
 #include "fwts.h"
 
@@ -33,5 +35,9 @@ char *fwts_pipe_read(const int fd, ssize_t *length);
 int   fwts_pipe_close(const int fd, const pid_t pid);
 int   fwts_pipe_exec(const char *command, fwts_list **list, int *status);
 int   fwts_exec(const char *command, int *status);
+int   fwts_write_string_to_file(FILE *file, const char *str);
+int   fwts_write_string_file(const char *file_name, const char *str);
+int   fwts_read_file_first_line(const char *file_name, char **line);
+bool  fwts_file_first_line_contains_string(const char *file_name, const char *str);
 
 #endif
diff --git a/src/lib/include/fwts_stringextras.h b/src/lib/include/fwts_stringextras.h
index 3d89fe6..0a2b9c7 100644
--- a/src/lib/include/fwts_stringextras.h
+++ b/src/lib/include/fwts_stringextras.h
@@ -24,5 +24,6 @@
 
 void fwts_chop_newline(char *str);
 char *fwts_realloc_strcat(char *orig, const char *newstr);
+char *fwts_string_endswith(const char *str, const char *postfix);
 
 #endif
diff --git a/src/lib/src/fwts_pipeio.c b/src/lib/src/fwts_pipeio.c
index df07295..7c1f719 100644
--- a/src/lib/src/fwts_pipeio.c
+++ b/src/lib/src/fwts_pipeio.c
@@ -1,6 +1,11 @@
 /*
  * Copyright (C) 2010-2014 Canonical
  *
+ * The following functions are derivative work from systemd, and
+ * are covered by Copyright 2010 Lennart Poettering:
+ *     fwts_write_string_to_file(), fwts_write_string_file(),
+ *     fwts_write_string_file(), fwts_read_file_first_line()
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
@@ -29,9 +34,26 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
 
 #include "fwts.h"
 
+static inline void freep(void *p)
+{
+	free(*(void**) p);
+}
+
+static inline void fclosep(FILE **file)
+{
+	if (*file)
+		fclose(*file);
+}
+
+
+#define _cleanup_free_ __attribute__((cleanup(freep)))
+#define _cleanup_fclose_ __attribute__((cleanup(fclosep)))
+
 /*
  *  fwts_pipe_open()
  *	execl a command, return pid in *childpid and
@@ -184,3 +206,89 @@ int fwts_exec(const char *command, int *status)
 		return FWTS_EXEC_ERROR;
 	return FWTS_OK;
 }
+
+/*
+ *  fwts_write_string_to_file()
+ *	write a string to a file pointer
+ *	Return 0 if writing worked, errno if it failed.
+ */
+int fwts_write_string_to_file(FILE *file, const char *str)
+{
+	errno = 0;
+	fputs(str, file);
+	if (!fwts_string_endswith(str, "\n"))
+		fputc('\n', file);
+
+	fflush(file);
+
+	if (ferror(file))
+		return errno ? -errno : -EIO;
+
+	return 0;
+}
+
+/*
+ *  fwts_write_string_file()
+ *	write a string to a file
+ *	Return 0 if writing worked, errno if it failed.
+ */
+int fwts_write_string_file(const char *file_name, const char *str) {
+	_cleanup_fclose_ FILE *file = NULL;
+
+	file = fopen(file_name, "we");
+	if (!file)
+		return -errno;
+
+	return fwts_write_string_to_file(file, str);
+}
+
+/*
+ *  fwts_read_file_first_line()
+ *	read the first line of a file
+ *	Return 0 if reading worked, errno if it failed.
+ */
+int fwts_read_file_first_line(const char *file_name, char **line)
+{
+	_cleanup_fclose_ FILE *file = NULL;
+	char buffer[LINE_MAX], *temp;
+
+	file = fopen(file_name, "re");
+	if (!file)
+		return -errno;
+
+	if (!fgets(buffer, sizeof(buffer), file)) {
+		if (ferror(file))
+			return errno ? -errno : -EIO;
+		buffer[0] = 0;
+	}
+
+	temp = strdup(buffer);
+	if (!temp)
+		return -ENOMEM;
+
+	fwts_chop_newline(temp);
+
+	*line = temp;
+	return 0;
+}
+
+/*
+ *  fwts_file_first_line_contains_string()
+ *	read the first line of a file
+ *	Return 0 if reading worked, errno if it failed.
+ */
+bool fwts_file_first_line_contains_string(const char *file_name, const char *str)
+{
+	_cleanup_free_ char *contents = NULL;
+	int ret;
+
+	ret = fwts_read_file_first_line(file_name, &contents);
+
+	if (ret < 0) {
+		fprintf(stderr, "Cannot get the contents of %s. Errno: %d\n",
+				file_name, ret);
+		return 0;
+	}
+
+	return (strstr(contents, str) != NULL);
+}
\ No newline at end of file
diff --git a/src/lib/src/fwts_stringextras.c b/src/lib/src/fwts_stringextras.c
index 7c3adac..a46f360 100644
--- a/src/lib/src/fwts_stringextras.c
+++ b/src/lib/src/fwts_stringextras.c
@@ -1,6 +1,10 @@
 /*
  * Copyright (C) 2010-2014 Canonical
  *
+ * The following functions are derivative work from systemd, and
+ * are covered by Copyright 2010 Lennart Poettering:
+ *     fwts_string_endswith()
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
@@ -60,3 +64,27 @@ char *fwts_realloc_strcat(char *orig, const char *newstr)
 	}
 	return orig;
 }
+
+/*
+ * fwts_string_endswith()
+ * see if str ends with postfix
+ * return NULL if fails, otherwise return the matched substring
+ */
+char* fwts_string_endswith(const char *str, const char *postfix)
+{
+	size_t sl, pl;
+
+	sl = strlen(str);
+	pl = strlen(postfix);
+
+	if (pl == 0)
+		return (char*) str + sl;
+
+	if (sl < pl)
+		return NULL;
+
+	if (memcmp(str + sl - pl, postfix, pl) != 0)
+		return NULL;
+
+	return (char*) str + sl - pl;
+}
\ No newline at end of file
-- 
1.9.1




More information about the fwts-devel mailing list