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
Ack - excellent work, thanks Radek. It's great to have those comments in there; I expect that will be a big help when maintaining this code in future.
cheers, Zane.
On 27/10/11 12:03, Radek Novacek wrote:
Functions for listing, setting and getting power management profiles for the host agent, including unittest. Based on patch by Jaroslav Skarvadajskarvad@redhat.com
Signed-off-by: Radek Novacekrnovacek@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
matahari@lists.fedorahosted.org