Functions for listing, setting and getting power management profiles for the host agent, including unittest. Based on patch by Jaroslav Skarvada jskarvad@redhat.com
Signed-off-by: Radek Novacek rnovacek@redhat.com --- matahari.spec.in | 2 + src/host/host-dbus.c | 79 +++++++++++ src/host/host-qmf.cpp | 31 ++++ src/host/org.matahariproject.Host.policy | 21 +++ src/host/schema.xml | 13 ++ src/include/matahari/host.h | 35 +++++ src/include/matahari/utilities.h | 12 ++ src/lib/host.c | 18 +++ src/lib/host_linux.c | 223 ++++++++++++++++++++++++++++++ src/lib/host_private.h | 28 ++++ src/lib/host_windows.c | 205 +++++++++++++++++++++++++++ src/lib/utilities.c | 25 ++++ src/unittests/mh_api_host.h | 37 +++++ 13 files changed, 729 insertions(+), 0 deletions(-)
diff --git a/matahari.spec.in b/matahari.spec.in index 8f105f4..83301c7 100644 --- a/matahari.spec.in +++ b/matahari.spec.in @@ -168,6 +168,8 @@ Requires: dmidecode Requires(post): chkconfig Requires(preun):chkconfig Requires(preun):initscripts +# Power management functions in host agent require tuned +Requires: tuned
%description host QMF agent for viewing and controlling remote hosts diff --git a/src/host/host-dbus.c b/src/host/host-dbus.c index 05b25f3..9ef8b6c 100644 --- a/src/host/host-dbus.c +++ b/src/host/host-dbus.c @@ -119,6 +119,85 @@ Host_set_uuid(Matahari* matahari, const char *lifetime, const char *uuid, DBusGM return TRUE; }
+gboolean +Host_set_power_profile(Matahari* matahari, const char *profile, DBusGMethodInvocation *context) +{ + GError *error = NULL; + enum mh_result res; + + if (!check_authorization(HOST_BUS_NAME ".set_power_profile", &error, context)) { + dbus_g_method_return_error(context, error); + g_error_free(error); + return FALSE; + } + res = mh_host_set_power_profile(profile); + if (res != MH_RES_SUCCESS) { + error = g_error_new(MATAHARI_ERROR, res, mh_result_to_str(res)); + dbus_g_method_return_error(context, error); + g_error_free(error); + return FALSE; + } + + dbus_g_method_return(context, 0); + return TRUE; +} + +gboolean +Host_get_power_profile(Matahari* matahari, DBusGMethodInvocation *context) +{ + char *profile = NULL; + GError *error = NULL; + enum mh_result res; + + if (!check_authorization(HOST_BUS_NAME ".get_power_profile", &error, context)) { + dbus_g_method_return_error(context, error); + g_error_free(error); + return FALSE; + } + res = mh_host_get_power_profile(&profile); + if (res != MH_RES_SUCCESS) { + error = g_error_new(MATAHARI_ERROR, res, mh_result_to_str(res)); + dbus_g_method_return_error(context, error); + g_error_free(error); + return FALSE; + } + dbus_g_method_return(context, profile); + free(profile); + return TRUE; +} + +gboolean +Host_list_power_profiles(Matahari* matahari, DBusGMethodInvocation *context) +{ + GError *error = NULL; + GList *list, *plist; + char **profiles; + int i = 0; + + if (!check_authorization(HOST_BUS_NAME ".list_power_profiles", &error, context)) { + dbus_g_method_return_error(context, error); + g_error_free(error); + return FALSE; + } + // Get the list of profiles + list = mh_host_list_power_profiles(); + + // Convert GList * with profiles to array (char **) + profiles = g_new(char *, g_list_length(list) + 1); + for (plist = g_list_first(list); + plist; + plist = g_list_next(plist)) { + profiles[i++] = plist->data; + } + profiles[i] = NULL; // Sentinel + + dbus_g_method_return(context, profiles); + g_list_free_full(list, free); + g_free(profiles); + return TRUE; +} + + /* Generated dbus stuff for host * MUST be after declaration of user defined functions. */ diff --git a/src/host/host-qmf.cpp b/src/host/host-qmf.cpp index 618cf9c..4b72d00 100644 --- a/src/host/host-qmf.cpp +++ b/src/host/host-qmf.cpp @@ -112,6 +112,7 @@ HostAgent::invoke(qmf::AgentSession session, qmf::AgentEvent event, return TRUE; }
+ enum mh_result res = MH_RES_SUCCESS; const std::string& methodName(event.getMethodName()); qpid::types::Variant::Map& args = event.getArguments();
@@ -153,7 +154,37 @@ HostAgent::invoke(qmf::AgentSession session, qmf::AgentEvent event, if (uuid) { event.addReturnArgument("uuid", uuid); } + } else if (methodName == "set_power_profile") { + res = mh_host_set_power_profile(args["profile"].asString().c_str()); + if (res != MH_RES_SUCCESS) { + session.raiseException(event, mh_result_to_str(res)); + goto bail; + } else { + event.addReturnArgument("status", 0); + } + } else if (methodName == "get_power_profile") { + char *profile = NULL; + res = mh_host_get_power_profile(&profile); + if (res != MH_RES_SUCCESS) { + session.raiseException(event, mh_result_to_str(res)); + goto bail; + } else { + event.addReturnArgument("profile", profile); + } + free(profile); + } else if (methodName == "list_power_profiles") { + GList *plist = NULL; + GList *profile_list = NULL;
+ _qtype::Variant::List s_list; + + profile_list = mh_host_list_power_profiles(); + for (plist = g_list_first(profile_list); plist; + plist = g_list_next(plist)) { + s_list.push_back((const char *) plist->data); + } + event.addReturnArgument("profiles", s_list); + g_list_free_full(profile_list, free); } else { session.raiseException(event, mh_result_to_str(MH_RES_NOT_IMPLEMENTED)); goto bail; diff --git a/src/host/org.matahariproject.Host.policy b/src/host/org.matahariproject.Host.policy index ed04a25..90636a8 100644 --- a/src/host/org.matahariproject.Host.policy +++ b/src/host/org.matahariproject.Host.policy @@ -200,4 +200,25 @@ <allow_active>auth_admin</allow_active> </defaults> </action> + <action id="org.matahariproject.Host.get_power_profile"> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>auth_admin</allow_active> + </defaults> + </action> + <action id="org.matahariproject.Host.set_power_profile"> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>auth_admin</allow_active> + </defaults> + </action> + <action id="org.matahariproject.Host.list_power_profiles"> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>auth_admin</allow_active> + </defaults> + </action> </policyconfig> diff --git a/src/host/schema.xml b/src/host/schema.xml index 1841907..f5b2092 100644 --- a/src/host/schema.xml +++ b/src/host/schema.xml @@ -90,6 +90,19 @@ <arg name="uuid" dir="I" type="sstr" /> <arg name="rc" dir="O" type="int32" /> </method> + + <method name="set_power_profile" desc="Set power management profile"> + <arg name="profile" dir="I" type="sstr" /> + <arg name="status" dir="O" type="uint32" /> + </method> + + <method name="get_power_profile" desc="Get current power management profile"> + <arg name="profile" dir="O" type="sstr" /> + </method> + + <method name="list_power_profiles" desc="List available power management profiles"> + <arg name="profiles" dir="O" type="list" /> + </method> </class>
<event name="heartbeat" args="timestamp,sequence,hostname,uuid" /> diff --git a/src/include/matahari/host.h b/src/include/matahari/host.h index 1da756a..31a5a98 100644 --- a/src/include/matahari/host.h +++ b/src/include/matahari/host.h @@ -28,6 +28,9 @@ #include <stdint.h> #include <stdlib.h> #include <sigar.h> +#include <glib.h> + +#include "matahari/errors.h"
/** * Get a UUID for a host. @@ -118,4 +121,36 @@ mh_host_get_load_averages(sigar_loadavg_t *avg); void mh_host_get_processes(sigar_proc_stat_t *procs);
+/** + * Set power management profile. + * + * \param[in] profile One of profile returned by mh_host_list_power_profiles + * function + * \return see enum mh_result + */ +enum mh_result +mh_host_set_power_profile(const char *profile); + +/** + * Get current power management profile. + * + * \param[out] profile Variable where current profile will be written to. + * Variable must be freed with free(). + * + * \return see enum mh_result + */ +enum mh_result +mh_host_get_power_profile(char **profile); + +/** + * Get list of all available power management profiles. + * + * \note The return of this routine must be freed with + * g_list_free_full(returned_list, free); + * + * \return list of all profiles. + */ +GList * +mh_host_list_power_profiles(void); + #endif // __MH_HOST_H__ diff --git a/src/include/matahari/utilities.h b/src/include/matahari/utilities.h index 893d068..5084261 100644 --- a/src/include/matahari/utilities.h +++ b/src/include/matahari/utilities.h @@ -128,4 +128,16 @@ mh_strlen_zero(const char *s) char * mh_string_copy(char *dst, const char *src, size_t dst_len);
+/** + * Read the whole content of file from file descriptor + * + * \param[in] fd File descriptor to read from. Must be readable. + * \param[out] data Data read from the file. Must be freed with free(). + * + * \retval 0 File is empty + * \retval >0 Number of characters read + * \retval <0 Error occured + */ +gsize +mh_read_from_fd(int fd, char **data); #endif diff --git a/src/lib/host.c b/src/lib/host.c index eb712c9..c6fb4c8 100644 --- a/src/lib/host.c +++ b/src/lib/host.c @@ -310,3 +310,21 @@ mh_host_set_uuid(const char *lifetime, const char *uuid)
return G_FILE_ERROR_NOSYS; } + +enum mh_result +mh_host_set_power_profile(const char* profile) +{ + return host_os_set_power_profile(profile); +} + +enum mh_result +mh_host_get_power_profile(char **profile) +{ + return host_os_get_power_profile(profile); +} + +GList * +mh_host_list_power_profiles(void) +{ + return host_os_list_power_profiles(); +} diff --git a/src/lib/host_linux.c b/src/lib/host_linux.c index 5b9a4f9..ac9c305 100644 --- a/src/lib/host_linux.c +++ b/src/lib/host_linux.c @@ -24,10 +24,12 @@ #include <limits.h> #include <string.h> #include <fcntl.h> +#include <errno.h> #include <sys/reboot.h> #include <sys/sysinfo.h> #include <sys/utsname.h> #include <sys/ioctl.h> +#include <sys/wait.h>
#include <linux/reboot.h> #include <linux/kd.h> @@ -45,6 +47,7 @@
#define UUID_STR_BUF_LEN 37
+#define BUFSIZE 4096
const char * host_os_get_cpu_flags(void) @@ -436,3 +439,223 @@ host_os_set_custom_uuid(const char *uuid) return rc; }
+static enum mh_result +exec_command(const char *apath, char *args[], int atimeout, char **stdoutbuf, + char **stderrbuf) +{ + enum mh_result res = MH_RES_SUCCESS; + GPid pid = 0; + GError *gerr = NULL; + int status = 0; + int timeout = atimeout; + gint stdout_fd; + gint stderr_fd; + + if (!args || !args[0]) + return MH_RES_OTHER_ERROR; + + mh_trace("Spawning '%s'\n", args[0]); + if (!g_spawn_async_with_pipes(apath, args, NULL, G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, &pid, NULL, &stdout_fd, + &stderr_fd, &gerr)) { + mh_perror(LOG_ERR, "Spawn_process failed with code %d, message: %s\n", + gerr->code, gerr->message); + return MH_RES_OTHER_ERROR; + } + + mh_trace("Waiting for %d", pid); + while (timeout > 0 && waitpid(pid, &status, WNOHANG) <= 0) { + sleep(1); + timeout--; + } + + if (timeout == 0) { + int killrc = sigar_proc_kill(pid, SIGKILL); + + res = MH_RES_BACKEND_ERROR; + mh_warn("%d - timed out after %dms", pid, atimeout); + + if (killrc != SIGAR_OK && killrc != ESRCH) { + mh_err("kill(%d, KILL) failed: %d", pid, killrc); + } + + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) > 0) + res = MH_RES_BACKEND_ERROR; + mh_err("Managed process %d exited with rc=%d", pid, WEXITSTATUS(status)); + + } else if (WIFSIGNALED(status)) { + int signo = WTERMSIG(status); + res = MH_RES_BACKEND_ERROR; + mh_err("Managed process %d exited with signal=%d", pid, signo); + } + + mh_trace("Child done: %d", pid); + + if (stdoutbuf) { + if (mh_read_from_fd(stdout_fd, stdoutbuf) < 0) { + mh_err("Unable to read standard output from command %s.", apath); + stdoutbuf = NULL; + } else { + mh_debug("stdout: %s", *stdoutbuf); + } + } + + if (stderrbuf) { + if (mh_read_from_fd(stderr_fd, stderrbuf) < 0) { + mh_err("Unable to read standard error output from command %s.", apath); + stderrbuf = NULL; + } else { + mh_debug("stderr: %s", *stderrbuf); + } + } + + close(stdout_fd); + close(stderr_fd); + + g_spawn_close_pid(pid); + + return res; +} + +GList * +host_os_list_power_profiles(void) +{ + char *stdoutbuf = NULL; + char *c1, *c2; + char *args[3] = {0}; + GList *list = NULL; + enum mh_result res; + int len; + + args[0] = TUNEDADM; + args[1] = TA_LISTPROFILES; + args[2] = NULL; + + res = exec_command(NULL, args, TIMEOUT, &stdoutbuf, NULL); + len = strlen(stdoutbuf); + + if (res == MH_RES_SUCCESS && stdoutbuf) { + c1 = stdoutbuf; + do { + // Each line with profile in "tuned-adm list" starts with "-" + if (*c1 == '-' && c1 - stdoutbuf + 2 < len) { + // Skip the dash and the space after it + c1 += 2; + // Profile name is the rest of the line + c2 = strchr(c1, '\n'); + if (c2 && c2 > c1) { + // Replace \n with \0 and append it to output + *c2 = '\0'; + list = g_list_append(list, strdup(c1)); + // Replace it back with \n, so free() will free whole string + *c2 = '\n'; + } + } else { + // Line doesn't contain the profile -> skip the line + c2 = strchr(c1, '\n'); + } + // Move c1 to beggining of the next line + if (c2) { + c1 = c2 + 1; + } + } while (c2 && c1 - stdoutbuf < len); + + if (g_list_length(list) == 0) { + // Return at least "off" profile, if no other profile found + list = g_list_append(list, strdup(TA_OFF)); + } + } + free(stdoutbuf); + + return list; +} + +static gboolean +check_profile(const char *profile) +{ + GList *list = NULL; + GList *llist; + gboolean rc = FALSE; + + if (!(list = host_os_list_power_profiles())) + return FALSE; + + if (!g_list_length(list)) + return FALSE; + + for (llist = g_list_first(list); llist && !rc; llist = g_list_next(llist)) { + mh_trace("comparing '%s' with '%s'", (char *) llist->data, profile); + if (!strcmp(profile, (char *) llist->data)) { + rc = TRUE; + break; + } + } + g_list_free_full(list, free); + + return rc; +} + +enum mh_result +host_os_set_power_profile(const char *profile) +{ + char *args[4] = {0}; + + args[0] = TUNEDADM; + if (!profile) + return MH_RES_INVALID_ARGS; + + if (!strcmp(profile, TA_OFF)) { + args[1] = TA_OFF; + mh_trace("switching tuning off"); + } else { + if (!check_profile(profile)) { + mh_err("invalid profile: %s", profile); + return MH_RES_INVALID_ARGS; + } + args[1] = TA_SETPROFILE; + mh_trace("setting profile: %s", profile); + } + + args[2] = (char *) profile; + args[3] = NULL; + + return exec_command(NULL, args, TIMEOUT, NULL, NULL); +} + +enum mh_result +host_os_get_power_profile(char **profile) +{ + char *stdoutbuf = NULL; + char *c1, *c2 = NULL; + char *args[3] = {0}; + enum mh_result res; + + args[0] = TUNEDADM; + args[1] = TA_GETPROFILE; + args[2] = NULL; + + res = exec_command(NULL, args, TIMEOUT, &stdoutbuf, NULL); + if (res == MH_RES_SUCCESS) { + // Parse first line of "tuned-adm active", that is something like + // "Current active profile: profile_name", so take what is after + // semicolon and space to the end of the line + if (stdoutbuf && (c1 = strchr(stdoutbuf, ':')) && + (c1 - stdoutbuf + 2 < strlen(stdoutbuf))) { + if ((c2 = strchr(c1, '\n'))) { + *c2 = '\0'; + } + // + 2 because we need to skip semicolon and space + *profile = strdup(c1 + 2); + if (c2) + *c2 = '\n'; + } else { + *profile = strdup(STR_UNK); + } + } else { + res = MH_RES_BACKEND_ERROR; + } + free(stdoutbuf); + + return res; +} diff --git a/src/lib/host_private.h b/src/lib/host_private.h index c2f7a1b..3a985d5 100644 --- a/src/lib/host_private.h +++ b/src/lib/host_private.h @@ -31,6 +31,25 @@
#include <glib.h>
+#define TIMEOUT 10 +#define TA_PATH "/usr/bin/" +#define TA_SETPROFILE "profile" +#define TA_GETPROFILE "active" +#define TA_LISTPROFILES "list" +#define TA_OFF "off" +#define TUNEDADM TA_PATH "tuned-adm" + +#define SYSTEM32 "system32" +#define POWERCFG "powercfg" +#define PC_SETPROFILE "-SETACTIVE" +#define PC_GETPROFILE "-GETACTIVESCHEME" +#define PC_LISTPROFILES "-LIST" +#define GUID "GUID: " + +#define STR_UNK "UNKNOWN" +#define STR_ERR "ERROR" + + const char * host_os_get_cpu_flags(void);
@@ -67,4 +86,13 @@ host_os_agent_uuid(void); int host_os_set_custom_uuid(const char *uuid);
+enum mh_result +host_os_set_power_profile(const char *profile); + +enum mh_result +host_os_get_power_profile(char **profile); + +GList * +host_os_list_power_profiles(void); + #endif /* __MH_HOST_PRIVATE_H__ */ diff --git a/src/lib/host_windows.c b/src/lib/host_windows.c index f53567c..fb8af0b 100644 --- a/src/lib/host_windows.c +++ b/src/lib/host_windows.c @@ -33,12 +33,15 @@
#include "matahari/logging.h" #include "matahari/host.h" +#include "matahari/errors.h" #include "host_private.h"
static const char CUSTOM_UUID_KEY[] = "CustomUUID";
static const char REBOOT_UUID_KEY[] = "RebootUUID";
+#define BUFSIZE 4096 + const char * host_os_get_cpu_flags(void) { @@ -394,3 +397,205 @@ host_os_set_custom_uuid(const char *uuid)
return ret; } + +static enum mh_result +exec_command(const char *apath, char *args[], int atimeout, char **stdoutbuf, + char **stderrbuf) +{ + enum mh_result res = MH_RES_SUCCESS; + DWORD status = 0; + gint stdout_fd; + gint stderr_fd; + HANDLE phandle = NULL; + GError *gerr = NULL; + + if (!args || !args[0]) + return MH_RES_OTHER_ERROR; + + /* convert to ms */ + atimeout = atimeout * 1000; + + mh_trace("Spawning '%s'\n", args[0]); + if (!g_spawn_async_with_pipes(apath, args, NULL, G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, (GPid *) &phandle, NULL, + &stdout_fd, &stderr_fd, &gerr)) { + mh_perror(LOG_ERR, "Spawn_process failed with code %d, message: %s\n", + gerr->code, gerr->message); + return MH_RES_OTHER_ERROR; + } + + mh_trace("Waiting for %p", (void *)phandle); + WaitForSingleObject(phandle, atimeout); + + if (GetExitCodeProcess(phandle, &status) == 0) { + mh_err("Could not get exit code: %lu", GetLastError()); + res = MH_RES_BACKEND_ERROR; + } + + if (res == MH_RES_BACKEND_ERROR) { + TerminateProcess(phandle, 1); + mh_err("%p - timed out after %dms", (void *)phandle, atimeout); + } + + /* Nice to log it somewhere */ + mh_debug("Result of '%s' was %d", args[0], (int)status); + + mh_trace("Child done: %p", (void *)phandle); + + if (stdoutbuf) { + if (mh_read_from_fd(stdout_fd, stdoutbuf) < 0) { + mh_err("Unable to read standard output from command %s.", apath); + stdoutbuf = NULL; + } else { + mh_debug("stdout: %s", *stdoutbuf); + } + } + + if (stderrbuf) { + if (mh_read_from_fd(stderr_fd, stderrbuf) < 0) { + mh_err("Unable to read standard error output from command %s.", apath); + stderrbuf = NULL; + } else { + mh_debug("stderr: %s", *stderrbuf); + } + } + + _close(stdout_fd); + _close(stderr_fd); + + g_spawn_close_pid((GPid *) phandle); + + return res; +} + +static gboolean +get_profiles(GList **names, GList **guids) +{ + gboolean rc = FALSE; + int len; + char *stdoutbuf = NULL; + char *c1, *c2; + char *args[3] = {0}; + + args[0] = SYSTEM32 "\" POWERCFG; + args[1] = PC_LISTPROFILES; + args[2] = NULL; + + if (exec_command(getenv("WINDIR"), args , TIMEOUT, &stdoutbuf, NULL) + == MH_RES_SUCCESS && stdoutbuf) { + rc = TRUE; + len = strlen(stdoutbuf); + c1 = stdoutbuf; + + // powercfg command returns lines with profiles, that looks like: + // "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced)" + // So we will take word after "GUID: " as GUID and word in brackets + // as name of the profile + while ((c1 = strstr(c1, GUID))) { + c1 += strlen(GUID); + // c1 is now after "GUID: " + if (c1 - stdoutbuf < len && (c2 = strchr(c1, ' '))) { + // c2 points to end of uuid, put \0 there + *c2 = '\0'; + // Add guid to the list + *guids = g_list_append(*guids, strdup(c1)); + // Put \n back, so the string can be freed + *c2 = '\n'; + // Now find name in brackets + if ((c1 = strchr(c2, '(')) && ++c1 - stdoutbuf < len && + (c2 = strchr(c1, ')'))) { + // Append profile name to the list + *c2 = '\0'; + *names = g_list_append(*names, strdup(c1)); + *c2 = ')'; + } + } + } + } + + if (!*names || !*guids || g_list_length(*names) != g_list_length(*guids)) + rc = FALSE; + + free(stdoutbuf); + return rc; +} + +enum mh_result +host_os_set_power_profile(const char *profile) +{ + enum mh_result res = MH_RES_SUCCESS; + char *guid = NULL; + GList *names = NULL; + GList *guids = NULL; + GList *pnames = NULL; + GList *pguids = NULL; + char *args[4] = {0}; + + if (!get_profiles(&names, &guids)) { + return MH_RES_BACKEND_ERROR; + } + + for (pnames = g_list_first(names), pguids = g_list_first(guids); + pnames && pguids && !guid; + pnames = g_list_next(pnames), pguids = g_list_next(pguids)) { + if (!strcmp((char *) pnames->data, profile)) { + guid = strdup((char *) pguids->data); + } + } + + g_list_free_full(names, free); + g_list_free_full(guids, free); + + if (guid) { + args[0] = SYSTEM32 "\" POWERCFG; + args[1] = PC_SETPROFILE; + args[2] = guid; + args[3] = NULL; + res = exec_command(getenv("WINDIR"), args, TIMEOUT, NULL, NULL); + free(guid); + } else { + res = MH_RES_INVALID_ARGS; + } + return res; +} + +enum mh_result +host_os_get_power_profile(char **profile) +{ + enum mh_result res; + char *stdoutbuf = NULL; + char *c1, *c2; + char *args[3] = {0}; + + args[0] = SYSTEM32 "\" POWERCFG; + args[1] = PC_GETPROFILE; + args[2] = NULL; + + res = exec_command(getenv("WINDIR"), args, TIMEOUT, &stdoutbuf, NULL); + if (res == MH_RES_SUCCESS) { + // Profile name is string between brackets + if (stdoutbuf && (c1 = strchr(stdoutbuf, '(')) && + ++c1 - stdoutbuf < strlen(stdoutbuf) && (c2 = strchr(c1, ')'))) { + *c2 = '\0'; + *profile = strdup(c1); + *c2 = ')'; + } else { + res = MH_RES_BACKEND_ERROR; + } + } + free(stdoutbuf); + + return res; +} + +GList * +host_os_list_power_profiles(void) +{ + GList *names = NULL; + GList *guids = NULL; + + get_profiles(&names, &guids); + g_list_free_full(guids, free); + + return names; +} diff --git a/src/lib/utilities.c b/src/lib/utilities.c index 8da6b44..d62f76e 100644 --- a/src/lib/utilities.c +++ b/src/lib/utilities.c @@ -545,3 +545,28 @@ mh_result_to_str(enum mh_result res) } return "FIXME: result code doesn't have message assigned!"; } + +gsize +mh_read_from_fd(int fd, char **data) +{ + gsize length; + GError *err = NULL; + GIOChannel *ch; + +#ifdef WIN32 + ch = g_io_channel_win32_new_fd(fd); +#else + ch = g_io_channel_unix_new(fd); +#endif + + if (g_io_channel_read_to_end(ch, data, &length, &err) != G_IO_STATUS_NORMAL) { + mh_err("Unable to read from filedescriptor %d: %s", fd, err->message); + free(*data); + data = NULL; + return -err->code; + } + g_io_channel_close(ch); + g_io_channel_unref(ch); + return length; +} + diff --git a/src/unittests/mh_api_host.h b/src/unittests/mh_api_host.h index 00287ca..56b3feb 100644 --- a/src/unittests/mh_api_host.h +++ b/src/unittests/mh_api_host.h @@ -163,6 +163,43 @@ class MhApiHostSuite : public CxxTest::TestSuite infomsg.str(""); }
+ void testPowerManagement(void) + { + char *original, *newProfile; + const char *profile; + GList *profiles; + guint len; + + srand(time(NULL)); + + // Save the original profile + TS_ASSERT(mh_host_get_power_profile(&original) == MH_RES_SUCCESS); + + // List all profiles + profiles = mh_host_list_power_profiles(); + len = g_list_length(profiles); + + // Check if at least one profile is present + TS_ASSERT(len > 0); + + // Choose random profile + profile = (const char *) g_list_nth(profiles, rand() % len)->data; + + // Set it + TS_ASSERT(mh_host_set_power_profile(profile) == MH_RES_SUCCESS); + + // Check if the profile is set + TS_ASSERT(mh_host_get_power_profile(&newProfile) == MH_RES_SUCCESS); + + TS_ASSERT(strcmp(newProfile, profile) == 0); + + // Restore original profile + TS_ASSERT(mh_host_set_power_profile(original) == MH_RES_SUCCESS); + + free(original); + free(newProfile); + g_list_free_full(profiles, free); + } };
#endif