This looks great, thanks Radek (& Jaroslav)! I've put some comments
inline, but they're mostly minor.
cheers,
Zane.
On 14/10/11 12:26, Radek Novacek wrote:
Functions for listing, setting and getting power management profiles
for
the host agent, including unittest. Based on patch by
Jaroslav Skarvada<jskarvad(a)redhat.com>
I didn't test it on windows, because I don't have windows machine around.
Please someone test it.
Signed-off-by: Radek Novacek<rnovacek(a)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 | 12 ++
src/include/matahari/host.h | 12 ++
src/lib/host.c | 18 +++
src/lib/host_linux.c | 227 ++++++++++++++++++++++++++++++
src/lib/host_private.h | 27 ++++
src/lib/host_windows.c | 209 +++++++++++++++++++++++++++
src/unittests/mh_api_host.h | 37 +++++
11 files changed, 675 insertions(+), 0 deletions(-)
diff --git a/matahari.spec.in b/matahari.spec.in
index 68404df..9615db8 100644
--- a/matahari.spec.in
+++ b/matahari.spec.in
@@ -154,6 +154,8 @@ Requires: %{name}-agent-lib = %{version}-%{release}
%ifarch i386 x86_64
Requires: dmidecode
%endif
+# 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..19a7c57 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_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_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_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 cb179cf..6f1ac32 100644
--- a/src/host/schema.xml
+++ b/src/host/schema.xml
@@ -92,6 +92,18 @@
<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..8f1cc61 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,13 @@ mh_host_get_load_averages(sigar_loadavg_t *avg);
void
mh_host_get_processes(sigar_proc_stat_t *procs);
+enum mh_result
+mh_host_set_power_profile(const char *profile);
+
+enum mh_result
+mh_host_get_power_profile(char **profile);
+
+GList *
+mh_host_list_power_profiles(void);
+
Doxygen comments here would be good, especially to note that the string
returned by mh_host_get_power_profile() has to be free()d.
#endif // __MH_HOST_H__
diff --git a/src/lib/host.c b/src/lib/host.c
index 29cb654..66c5baf 100644
--- a/src/lib/host.c
+++ b/src/lib/host.c
@@ -311,3 +311,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 304592a..7be51b7 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)
@@ -422,3 +425,227 @@ host_os_set_custom_uuid(const char *uuid)
return rc;
}
+static void
+read_output(int fd, char **data)
+{
+ int bytes = 0, len = 0;
+ char buf[BUFSIZE];
+
+ while ((((bytes = read(fd, buf, BUFSIZE - 1))> 0)&&
+ (*data = realloc(*data, len + bytes + 1))) ||
+ errno == EINTR)
So, technically if realloc returns NULL here, you've leaked some memory.
I would be inclined to reduce the realloc()ing by starting at BUFSIZE
and just doubling it every time it gets full, rather than resizing on
every read.
+ if (bytes> 0) {
+ buf[bytes] = 0;
+ mh_info("Read %d: %.*s", len, bytes, buf);
+ sprintf(*data + len, "%s", buf);
memcpy() seems a more obvious choice here?
+ len += bytes;
+ }
+
+ mh_info("Read %d bytes", len);
+}
+
+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);
+
+ read_output(stdout_fd, stdoutbuf);
+ read_output(stderr_fd, stderrbuf);
+
+ if (*stdoutbuf)
+ mh_debug("stdout: %s", *stdoutbuf);
+ if (*stderrbuf)
+ mh_debug("stderr: %s", *stderrbuf);
I'm a big fan of braces even where they're not strictly required.
(Insert your own punchline here.)
+
+ close(stdout_fd);
+ close(stderr_fd);
+
+ g_spawn_close_pid(pid);
+
+ return res;
+}
+
+GList *
+host_os_list_power_profiles(void)
+{
+ char *stdoutbuf = NULL;
+ char *stderrbuf = NULL;
+ char *c1, *c2;
+ char *args[3] = {0};
+ int cnt = 0;
+ GList *list = NULL;
+ enum mh_result res;
+
+ args[0] = TUNEDADM;
+ args[1] = TA_LISTPROFILES;
+ args[2] = NULL;
+
+ res = exec_command(NULL, args, TIMEOUT,&stdoutbuf,&stderrbuf);
+ if (res == MH_RES_SUCCESS&& stdoutbuf) {
+ c1 = stdoutbuf;
+ do {
+ if (*c1 == '-') {
+ c2 = strchr(c1++, '\n');
+ if (c2> c1) {
Getting ultra-nitpicky here, but I would prefer "if (c2 && c2 > c1)"
+ *c2 = 0;
+ list = g_list_append(list, strdup(++c1));
c1 has already been incremented once. Is it intentional to do it again?
A comment detailing what format we're trying to parse here might be helpful.
+ *c2 = '\n';
+ cnt++;
+ }
+ }
+ c2 = strchr(c1, '\n');
+ if (c2)
+ c1 = ++c2;
Braces
+ }
+ while (c2&& c1 - stdoutbuf< strlen(stdoutbuf));
This should be on the same line as the closing brace.
+
+ if (cnt)
+ list = g_list_append(list, strdup(TA_OFF));
Braces
+ }
+
+ free(stdoutbuf);
+ free(stderrbuf);
+
+ 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 *stdoutbuf = NULL;
+ char *stderrbuf = NULL;
+ char *args[4] = {0};
+ enum mh_result res;
+
+ 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;
+
+ res = exec_command(NULL, args, TIMEOUT,&stdoutbuf,&stderrbuf);
It would be good if it were allowed to pass NULL for the buffers when
they are not required.
+
+ free(stdoutbuf);
+ free(stderrbuf);
+
+ return res;
+}
+
+enum mh_result
+host_os_get_power_profile(char **profile)
+{
+ char *stdoutbuf = NULL;
+ char *stderrbuf = NULL;
+ char *c1, *c2;
+ 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,&stderrbuf);
+ if (res == MH_RES_SUCCESS) {
+ if (stdoutbuf&& strlen(stdoutbuf)&& (c1 = strchr(stdoutbuf,
':'))&&
+ (++c1 - stdoutbuf< strlen(stdoutbuf))) {
+ if ((c2 = strchr(stdoutbuf, '\n')))
+ *c2 = 0;
+ *profile = strdup(++c1);
+ }
+ else
+ *profile = strdup(STR_UNK);
+ }
+ else
+ res = MH_RES_BACKEND_ERROR;
Braces
+
+ free(stdoutbuf);
+ free(stderrbuf);
+
+ return res;
+}
diff --git a/src/lib/host_private.h b/src/lib/host_private.h
index c2f7a1b..b71cf3c 100644
--- a/src/lib/host_private.h
+++ b/src/lib/host_private.h
@@ -31,6 +31,24 @@
#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 STR_UNK "UNKNOWN"
+#define STR_ERR "ERROR"
+
+
const char *
host_os_get_cpu_flags(void);
@@ -67,4 +85,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..10bf5be 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,209 @@ host_os_set_custom_uuid(const char *uuid)
return ret;
}
+
+static void
+read_output(gint h, char **data)
+{
+ int len = 0;
+ int bytes = 0;
+ char buf[BUFSIZE];
+
+ while ((bytes = _read(h, buf, BUFSIZE - 1))> 0&&
+ (*data = realloc(*data, len + bytes + 1)))
+ {
+ buf[bytes] = 0;
+ mh_info("Read %d: %.*s", len, bytes, buf);
+ sprintf(*data + len, "%s", buf);
+ len += bytes;
+ }
+
+ mh_info("Read %d bytes", len);
+}
Same comments as for the linux one.
+
+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);
+
+ read_output(stdout_fd, stdoutbuf);
+ read_output(stderr_fd, stderrbuf);
+
+ if (*stdoutbuf)
+ mh_debug("stdout: %s", *stdoutbuf);
+ if (*stderrbuf)
+ 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 *stderrbuf = 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,&stderrbuf)
+ == MH_RES_SUCCESS&& stdoutbuf) {
+ rc = TRUE;
+ len = strlen(stdoutbuf);
+ c1 = stdoutbuf;
+
+ while ((c1 = strstr(c1, "GUID: "))) {
+ c1 += 6;
"GUID: " is worth a #define so we can use strlen() here.
+ if (c1 - stdoutbuf< len&& (c2 = strchr(c1,
' '))) {
+ *c2++ = 0;
+ *guids = g_list_append(*guids, strdup(c1));
+ if ((c1 = strchr(c2, '('))&& ++c1 - stdoutbuf<
len&&
+ (c2 = strchr(c1, ')'))) {
+ *c2 = 0;
+ *names = g_list_append(*names, strdup(c1));
+ *c2 = ')';
+ }
+ }
This looks a lot like a reimplementation of strtok()?
+ }
+ }
+
+ if (!*names || !*guids || g_list_length(*names) != g_list_length(*guids))
+ rc = FALSE;
+
+ free(stdoutbuf);
+ free(stderrbuf);
+
+ return rc;
+}
+
+enum mh_result
+host_os_set_power_profile(const char *profile)
+{
+ enum mh_result res = MH_RES_SUCCESS;
+ char *guid = NULL;
+ char *stdoutbuf = NULL;
+ char *stderrbuf = 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);
Definitely need braces here
+
+ 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,&stdoutbuf,
+&stderrbuf);
+ free(guid);
+ } else
+ res = MH_RES_INVALID_ARGS;
+
+ free(stdoutbuf);
+ free(stderrbuf);
+
+ return res;
+}
+
+enum mh_result
+host_os_get_power_profile(char **profile)
+{
+ enum mh_result res;
+ char *stdoutbuf = NULL;
+ char *stderrbuf = 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,&stderrbuf);
+ if (res == MH_RES_SUCCESS) {
+ if (stdoutbuf&& (c1 = strchr(stdoutbuf, '('))&&
+ ++c1 - stdoutbuf< strlen(stdoutbuf)&& (c2 = strchr(c1,
')'))) {
+ *c2 = 0;
+ *profile = strdup(c1);
+ } else
+ res = MH_RES_BACKEND_ERROR;
+ }
+
+ free(stdoutbuf);
+ free(stderrbuf);
+
+ 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/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