/*
* virsh-domain-event.c: Domain event listening commands
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*/
#include
#include "virsh-util.h"
#include "internal.h"
#include "viralloc.h"
#include "virenum.h"
#include "virtime.h"
#include "virtypedparam.h"
/*
* "event" command
*/
VIR_ENUM_DECL(virshDomainEvent);
VIR_ENUM_IMPL(virshDomainEvent,
VIR_DOMAIN_EVENT_LAST,
N_("Defined"),
N_("Undefined"),
N_("Started"),
N_("Suspended"),
N_("Resumed"),
N_("Stopped"),
N_("Shutdown"),
N_("PMSuspended"),
N_("Crashed"));
static const char *
virshDomainEventToString(int event)
{
const char *str = virshDomainEventTypeToString(event);
return str ? _(str) : _("unknown");
}
VIR_ENUM_DECL(virshDomainEventDefined);
VIR_ENUM_IMPL(virshDomainEventDefined,
VIR_DOMAIN_EVENT_DEFINED_LAST,
N_("Added"),
N_("Updated"),
N_("Renamed"),
N_("Snapshot"));
VIR_ENUM_DECL(virshDomainEventUndefined);
VIR_ENUM_IMPL(virshDomainEventUndefined,
VIR_DOMAIN_EVENT_UNDEFINED_LAST,
N_("Removed"),
N_("Renamed"));
VIR_ENUM_DECL(virshDomainEventStarted);
VIR_ENUM_IMPL(virshDomainEventStarted,
VIR_DOMAIN_EVENT_STARTED_LAST,
N_("Booted"),
N_("Migrated"),
N_("Restored"),
N_("Snapshot"),
N_("Event wakeup"));
VIR_ENUM_DECL(virshDomainEventSuspended);
VIR_ENUM_IMPL(virshDomainEventSuspended,
VIR_DOMAIN_EVENT_SUSPENDED_LAST,
N_("Paused"),
N_("Migrated"),
N_("I/O Error"),
N_("Watchdog"),
N_("Restored"),
N_("Snapshot"),
N_("API error"),
N_("Post-copy"),
N_("Post-copy Error"));
VIR_ENUM_DECL(virshDomainEventResumed);
VIR_ENUM_IMPL(virshDomainEventResumed,
VIR_DOMAIN_EVENT_RESUMED_LAST,
N_("Unpaused"),
N_("Migrated"),
N_("Snapshot"),
N_("Post-copy"),
N_("Post-copy Error"));
VIR_ENUM_DECL(virshDomainEventStopped);
VIR_ENUM_IMPL(virshDomainEventStopped,
VIR_DOMAIN_EVENT_STOPPED_LAST,
N_("Shutdown"),
N_("Destroyed"),
N_("Crashed"),
N_("Migrated"),
N_("Saved"),
N_("Failed"),
N_("Snapshot"));
VIR_ENUM_DECL(virshDomainEventShutdown);
VIR_ENUM_IMPL(virshDomainEventShutdown,
VIR_DOMAIN_EVENT_SHUTDOWN_LAST,
N_("Finished"),
N_("Finished after guest request"),
N_("Finished after host request"));
VIR_ENUM_DECL(virshDomainEventPMSuspended);
VIR_ENUM_IMPL(virshDomainEventPMSuspended,
VIR_DOMAIN_EVENT_PMSUSPENDED_LAST,
N_("Memory"),
N_("Disk"));
VIR_ENUM_DECL(virshDomainEventCrashed);
VIR_ENUM_IMPL(virshDomainEventCrashed,
VIR_DOMAIN_EVENT_CRASHED_LAST,
N_("Panicked"),
N_("Crashloaded"));
static const char *
virshDomainEventDetailToString(int event, int detail)
{
const char *str = NULL;
switch ((virDomainEventType) event) {
case VIR_DOMAIN_EVENT_DEFINED:
str = virshDomainEventDefinedTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_UNDEFINED:
str = virshDomainEventUndefinedTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_STARTED:
str = virshDomainEventStartedTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_SUSPENDED:
str = virshDomainEventSuspendedTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_RESUMED:
str = virshDomainEventResumedTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_STOPPED:
str = virshDomainEventStoppedTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_SHUTDOWN:
str = virshDomainEventShutdownTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_PMSUSPENDED:
str = virshDomainEventPMSuspendedTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_CRASHED:
str = virshDomainEventCrashedTypeToString(detail);
break;
case VIR_DOMAIN_EVENT_LAST:
break;
}
return str ? _(str) : _("unknown");
}
VIR_ENUM_DECL(virshGraphicsPhase);
VIR_ENUM_IMPL(virshGraphicsPhase,
VIR_DOMAIN_EVENT_GRAPHICS_LAST,
N_("connect"),
N_("initialize"),
N_("disconnect"));
static const char *
virshGraphicsPhaseToString(int phase)
{
const char *str = virshGraphicsPhaseTypeToString(phase);
return str ? _(str) : _("unknown");
}
VIR_ENUM_DECL(virshGraphicsAddress);
VIR_ENUM_IMPL(virshGraphicsAddress,
VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_LAST,
"IPv4",
"IPv6",
"unix");
static const char *
virshGraphicsAddressToString(int family)
{
const char *str = virshGraphicsAddressTypeToString(family);
if (str)
return str;
return _("unknown");
}
VIR_ENUM_DECL(virshDomainBlockJobStatus);
VIR_ENUM_IMPL(virshDomainBlockJobStatus,
VIR_DOMAIN_BLOCK_JOB_LAST,
N_("completed"),
N_("failed"),
N_("canceled"),
N_("ready"));
static const char *
virshDomainBlockJobStatusToString(int status)
{
const char *str = virshDomainBlockJobStatusTypeToString(status);
return str ? _(str) : _("unknown");
}
VIR_ENUM_DECL(virshDomainEventDiskChange);
VIR_ENUM_IMPL(virshDomainEventDiskChange,
VIR_DOMAIN_EVENT_DISK_CHANGE_LAST,
N_("changed"),
N_("dropped"));
static const char *
virshDomainEventDiskChangeToString(int reason)
{
const char *str = virshDomainEventDiskChangeTypeToString(reason);
return str ? _(str) : _("unknown");
}
struct virshDomainEventCallback {
const char *name;
virConnectDomainEventGenericCallback cb;
};
typedef struct virshDomainEventCallback virshDomainEventCallback;
struct virshDomEventData {
vshControl *ctl;
int event;
bool loop;
int *count;
bool timestamp;
virshDomainEventCallback *cb;
int id;
};
typedef struct virshDomEventData virshDomEventData;
static void G_GNUC_PRINTF(2, 3)
virshEventPrintf(virshDomEventData *data,
const char *fmt,
...)
{
va_list ap;
if (!data->loop && *data->count)
return;
if (data->timestamp) {
char timestamp[VIR_TIME_STRING_BUFLEN] = "";
ignore_value(virTimeStringNowRaw(timestamp));
vshPrint(data->ctl, "%s: ", timestamp);
}
va_start(ap, fmt);
vshPrintVa(data->ctl, fmt, ap);
va_end(ap);
(*data->count)++;
if (!data->loop)
vshEventDone(data->ctl);
}
/**
* virshEventPrint:
*
* @data: opaque data passed to all event callbacks
* @buf: string buffer describing the event
*
* Print the event description found in @buf and update virshDomEventData.
*
* This function resets @buf and frees all memory consumed by its content.
*/
static void
virshEventPrint(virshDomEventData *data,
virBuffer *buf)
{
g_autofree char *msg = NULL;
if (!(msg = virBufferContentAndReset(buf)))
return;
virshEventPrintf(data, "%s", msg);
}
static void
virshEventGenericPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event '%1$s' for domain '%2$s'\n"),
((virshDomEventData *) opaque)->cb->name,
virDomainGetName(dom));
virshEventPrint(opaque, &buf);
}
static void
virshEventLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
int event,
int detail,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'lifecycle' for domain '%1$s': %2$s %3$s\n"),
virDomainGetName(dom),
virshDomainEventToString(event),
virshDomainEventDetailToString(event, detail));
virshEventPrint(opaque, &buf);
}
static void
virshEventRTCChangePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
long long utcoffset,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'rtc-change' for domain '%1$s': %2$lld\n"),
virDomainGetName(dom),
utcoffset);
virshEventPrint(opaque, &buf);
}
static void
virshEventWatchdogPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
int action,
void *opaque)
{
switch ((virDomainEventWatchdogAction) action) {
case VIR_DOMAIN_EVENT_WATCHDOG_NONE:
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': none\n"),
virDomainGetName(dom));
break;
case VIR_DOMAIN_EVENT_WATCHDOG_PAUSE:
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': pause\n"),
virDomainGetName(dom));
break;
case VIR_DOMAIN_EVENT_WATCHDOG_RESET:
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': reset\n"),
virDomainGetName(dom));
break;
case VIR_DOMAIN_EVENT_WATCHDOG_POWEROFF:
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': poweroff\n"),
virDomainGetName(dom));
break;
case VIR_DOMAIN_EVENT_WATCHDOG_SHUTDOWN:
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': shutdown\n"),
virDomainGetName(dom));
break;
case VIR_DOMAIN_EVENT_WATCHDOG_DEBUG:
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': debug\n"),
virDomainGetName(dom));
break;
case VIR_DOMAIN_EVENT_WATCHDOG_INJECTNMI:
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': inject-nmi\n"),
virDomainGetName(dom));
break;
case VIR_DOMAIN_EVENT_WATCHDOG_LAST:
default:
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': unknown\n"),
virDomainGetName(dom));
break;
}
}
static void
virshEventIOErrorPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *srcPath,
const char *devAlias,
int action,
void *opaque)
{
switch ((virDomainEventIOErrorAction) action) {
case VIR_DOMAIN_EVENT_IO_ERROR_NONE:
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) none\n"),
virDomainGetName(dom), srcPath, devAlias);
break;
case VIR_DOMAIN_EVENT_IO_ERROR_PAUSE:
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) pause\n"),
virDomainGetName(dom), srcPath, devAlias);
break;
case VIR_DOMAIN_EVENT_IO_ERROR_REPORT:
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) report\n"),
virDomainGetName(dom), srcPath, devAlias);
break;
case VIR_DOMAIN_EVENT_IO_ERROR_LAST:
default:
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) unknown\n"),
virDomainGetName(dom), srcPath, devAlias);
break;
}
}
static void
virshEventGraphicsPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
int phase,
const virDomainEventGraphicsAddress *local,
const virDomainEventGraphicsAddress *remote,
const char *authScheme,
const virDomainEventGraphicsSubject *subject,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
size_t i;
virBufferAsprintf(&buf, _("event 'graphics' for domain '%1$s': %2$s local[%3$s %4$s %5$s] remote[%6$s %7$s %8$s] %9$s\n"),
virDomainGetName(dom),
virshGraphicsPhaseToString(phase),
virshGraphicsAddressToString(local->family),
local->node,
local->service,
virshGraphicsAddressToString(remote->family),
remote->node,
remote->service,
authScheme);
for (i = 0; i < subject->nidentity; i++) {
virBufferAsprintf(&buf, "\t%s=%s\n",
subject->identities[i].type,
subject->identities[i].name);
}
virshEventPrint(opaque, &buf);
}
static void
virshEventIOErrorReasonPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *srcPath,
const char *devAlias,
int action,
const char *reason,
void *opaque)
{
switch ((virDomainEventIOErrorAction) action) {
case VIR_DOMAIN_EVENT_IO_ERROR_NONE:
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) none due to %4$s\n"),
virDomainGetName(dom), srcPath, devAlias, reason);
break;
case VIR_DOMAIN_EVENT_IO_ERROR_PAUSE:
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) pause due to %4$s\n"),
virDomainGetName(dom), srcPath, devAlias, reason);
break;
case VIR_DOMAIN_EVENT_IO_ERROR_REPORT:
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) report due to %4$s\n"),
virDomainGetName(dom), srcPath, devAlias, reason);
break;
case VIR_DOMAIN_EVENT_IO_ERROR_LAST:
default:
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) unknown due to %4$s\n"),
virDomainGetName(dom), srcPath, devAlias, reason);
break;
}
}
static void
virshEventBlockJobPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *disk,
int type,
int status,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event '%1$s' for domain '%2$s': %3$s for %4$s %5$s\n"),
((virshDomEventData *) opaque)->cb->name,
virDomainGetName(dom),
virshDomainBlockJobToString(type),
disk,
virshDomainBlockJobStatusToString(status));
virshEventPrint(opaque, &buf);
}
static void
virshEventDiskChangePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *oldSrc,
const char *newSrc,
const char *alias,
int reason,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'disk-change' for domain '%1$s' disk %2$s: %3$s -> %4$s: %5$s\n"),
virDomainGetName(dom),
alias,
NULLSTR(oldSrc),
NULLSTR(newSrc),
virshDomainEventDiskChangeToString(reason));
virshEventPrint(opaque, &buf);
}
static void
virshEventTrayChangePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *alias,
int reason,
void *opaque)
{
switch ((virDomainEventTrayChangeReason) reason) {
case VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN:
virshEventPrintf(opaque, _("event 'tray-change' for domain '%1$s' disk %2$s: opened\n"),
virDomainGetName(dom), alias);
break;
case VIR_DOMAIN_EVENT_TRAY_CHANGE_CLOSE:
virshEventPrintf(opaque, _("event 'tray-change' for domain '%1$s' disk %2$s: closed\n"),
virDomainGetName(dom), alias);
break;
case VIR_DOMAIN_EVENT_TRAY_CHANGE_LAST:
default:
virshEventPrintf(opaque, _("event 'tray-change' for domain '%1$s' disk %2$s: unknown\n"),
virDomainGetName(dom), alias);
break;
}
}
static void
virshEventPMChangePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
int reason G_GNUC_UNUSED,
void *opaque)
{
/* As long as libvirt.h doesn't define any reasons, we might as
* well treat all PM state changes as generic events. */
virshEventGenericPrint(conn, dom, opaque);
}
static void
virshEventBalloonChangePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
unsigned long long actual,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'balloon-change' for domain '%1$s': %2$lluKiB\n"),
virDomainGetName(dom),
actual);
virshEventPrint(opaque, &buf);
}
static void
virshEventDeviceRemovedPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *alias,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'device-removed' for domain '%1$s': %2$s\n"),
virDomainGetName(dom),
alias);
virshEventPrint(opaque, &buf);
}
static void
virshEventDeviceAddedPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *alias,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'device-added' for domain '%1$s': %2$s\n"),
virDomainGetName(dom),
alias);
virshEventPrint(opaque, &buf);
}
static void
virshEventTunablePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
virTypedParameterPtr params,
int nparams,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
size_t i;
char *value;
virBufferAsprintf(&buf, _("event 'tunable' for domain '%1$s':\n"),
virDomainGetName(dom));
for (i = 0; i < nparams; i++) {
value = virTypedParameterToString(¶ms[i]);
if (value) {
virBufferAsprintf(&buf, "\t%s: %s\n", params[i].field, value);
VIR_FREE(value);
}
}
virshEventPrint(opaque, &buf);
}
VIR_ENUM_DECL(virshEventAgentLifecycleState);
VIR_ENUM_IMPL(virshEventAgentLifecycleState,
VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_LAST,
N_("unknown"),
N_("connected"),
N_("disconnected"));
VIR_ENUM_DECL(virshEventAgentLifecycleReason);
VIR_ENUM_IMPL(virshEventAgentLifecycleReason,
VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_LAST,
N_("unknown"),
N_("domain started"),
N_("channel event"));
#define UNKNOWNSTR(str) (str ? str : N_("unsupported value"))
static void
virshEventAgentLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
int state,
int reason,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'agent-lifecycle' for domain '%1$s': state: '%2$s' reason: '%3$s'\n"),
virDomainGetName(dom),
UNKNOWNSTR(virshEventAgentLifecycleStateTypeToString(state)),
UNKNOWNSTR(virshEventAgentLifecycleReasonTypeToString(reason)));
virshEventPrint(opaque, &buf);
}
static void
virshEventMigrationIterationPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
int iteration,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'migration-iteration' for domain '%1$s': iteration: '%2$d'\n"),
virDomainGetName(dom),
iteration);
virshEventPrint(opaque, &buf);
}
static void
virshEventJobCompletedPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
virTypedParameterPtr params,
int nparams,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
size_t i;
virBufferAsprintf(&buf, _("event 'job-completed' for domain '%1$s':\n"),
virDomainGetName(dom));
for (i = 0; i < nparams; i++) {
g_autofree char *value = virTypedParameterToString(¶ms[i]);
if (value)
virBufferAsprintf(&buf, "\t%s: %s\n", params[i].field, value);
}
virshEventPrint(opaque, &buf);
}
static void
virshEventDeviceRemovalFailedPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *alias,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'device-removal-failed' for domain '%1$s': %2$s\n"),
virDomainGetName(dom),
alias);
virshEventPrint(opaque, &buf);
}
VIR_ENUM_DECL(virshEventMetadataChangeType);
VIR_ENUM_IMPL(virshEventMetadataChangeType,
VIR_DOMAIN_METADATA_LAST,
N_("description"),
N_("title"),
N_("element"));
static void
virshEventMetadataChangePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
int type,
const char *nsuri,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'metadata-change' for domain '%1$s': type %2$s, uri %3$s\n"),
virDomainGetName(dom),
UNKNOWNSTR(virshEventMetadataChangeTypeTypeToString(type)),
NULLSTR(nsuri));
virshEventPrint(opaque, &buf);
}
static void
virshEventBlockThresholdPrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *dev,
const char *path,
unsigned long long threshold,
unsigned long long excess,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'block-threshold' for domain '%1$s': dev: %2$s(%3$s) %4$llu %5$llu\n"),
virDomainGetName(dom),
dev, NULLSTR(path), threshold, excess);
virshEventPrint(opaque, &buf);
}
VIR_ENUM_DECL(virshEventMemoryFailureRecipientType);
VIR_ENUM_IMPL(virshEventMemoryFailureRecipientType,
VIR_DOMAIN_EVENT_MEMORY_FAILURE_RECIPIENT_LAST,
N_("hypervisor"),
N_("guest"));
VIR_ENUM_DECL(virshEventMemoryFailureActionType);
VIR_ENUM_IMPL(virshEventMemoryFailureActionType,
VIR_DOMAIN_EVENT_MEMORY_FAILURE_ACTION_LAST,
N_("ignore"),
N_("inject"),
N_("fatal"),
N_("reset"));
static void
virshEventMemoryFailurePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
int recipient,
int action,
unsigned int flags,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, _("event 'memory-failure' for domain '%1$s':\nrecipient: %2$s\naction: %3$s\n"),
virDomainGetName(dom),
UNKNOWNSTR(virshEventMemoryFailureRecipientTypeTypeToString(recipient)),
UNKNOWNSTR(virshEventMemoryFailureActionTypeTypeToString(action)));
virBufferAsprintf(&buf, _("flags:\n\taction required: %1$d\n\trecursive: %2$d\n"),
!!(flags & VIR_DOMAIN_MEMORY_FAILURE_ACTION_REQUIRED),
!!(flags & VIR_DOMAIN_MEMORY_FAILURE_RECURSIVE));
virshEventPrint(opaque, &buf);
}
static void
virshEventMemoryDeviceSizeChangePrint(virConnectPtr conn G_GNUC_UNUSED,
virDomainPtr dom,
const char *alias,
unsigned long long size,
void *opaque)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf,
_("event 'memory-device-size-change' for domain '%1$s':\nalias: %2$s\nsize: %3$llu\n"),
virDomainGetName(dom), alias, size);
virshEventPrint(opaque, &buf);
}
virshDomainEventCallback virshDomainEventCallbacks[] = {
{ "lifecycle",
VIR_DOMAIN_EVENT_CALLBACK(virshEventLifecyclePrint), },
{ "reboot", virshEventGenericPrint, },
{ "rtc-change",
VIR_DOMAIN_EVENT_CALLBACK(virshEventRTCChangePrint), },
{ "watchdog",
VIR_DOMAIN_EVENT_CALLBACK(virshEventWatchdogPrint), },
{ "io-error",
VIR_DOMAIN_EVENT_CALLBACK(virshEventIOErrorPrint), },
{ "graphics",
VIR_DOMAIN_EVENT_CALLBACK(virshEventGraphicsPrint), },
{ "io-error-reason",
VIR_DOMAIN_EVENT_CALLBACK(virshEventIOErrorReasonPrint), },
{ "control-error", virshEventGenericPrint, },
{ "block-job",
VIR_DOMAIN_EVENT_CALLBACK(virshEventBlockJobPrint), },
{ "disk-change",
VIR_DOMAIN_EVENT_CALLBACK(virshEventDiskChangePrint), },
{ "tray-change",
VIR_DOMAIN_EVENT_CALLBACK(virshEventTrayChangePrint), },
{ "pm-wakeup",
VIR_DOMAIN_EVENT_CALLBACK(virshEventPMChangePrint), },
{ "pm-suspend",
VIR_DOMAIN_EVENT_CALLBACK(virshEventPMChangePrint), },
{ "balloon-change",
VIR_DOMAIN_EVENT_CALLBACK(virshEventBalloonChangePrint), },
{ "pm-suspend-disk",
VIR_DOMAIN_EVENT_CALLBACK(virshEventPMChangePrint), },
{ "device-removed",
VIR_DOMAIN_EVENT_CALLBACK(virshEventDeviceRemovedPrint), },
{ "block-job-2",
VIR_DOMAIN_EVENT_CALLBACK(virshEventBlockJobPrint), },
{ "tunable",
VIR_DOMAIN_EVENT_CALLBACK(virshEventTunablePrint), },
{ "agent-lifecycle",
VIR_DOMAIN_EVENT_CALLBACK(virshEventAgentLifecyclePrint), },
{ "device-added",
VIR_DOMAIN_EVENT_CALLBACK(virshEventDeviceAddedPrint), },
{ "migration-iteration",
VIR_DOMAIN_EVENT_CALLBACK(virshEventMigrationIterationPrint), },
{ "job-completed",
VIR_DOMAIN_EVENT_CALLBACK(virshEventJobCompletedPrint), },
{ "device-removal-failed",
VIR_DOMAIN_EVENT_CALLBACK(virshEventDeviceRemovalFailedPrint), },
{ "metadata-change",
VIR_DOMAIN_EVENT_CALLBACK(virshEventMetadataChangePrint), },
{ "block-threshold",
VIR_DOMAIN_EVENT_CALLBACK(virshEventBlockThresholdPrint), },
{ "memory-failure",
VIR_DOMAIN_EVENT_CALLBACK(virshEventMemoryFailurePrint), },
{ "memory-device-size-change",
VIR_DOMAIN_EVENT_CALLBACK(virshEventMemoryDeviceSizeChangePrint), },
};
G_STATIC_ASSERT(VIR_DOMAIN_EVENT_ID_LAST == G_N_ELEMENTS(virshDomainEventCallbacks));
static char **
virshDomainEventNameCompleter(vshControl *ctl G_GNUC_UNUSED,
const vshCmd *cmd G_GNUC_UNUSED,
unsigned int flags)
{
size_t i = 0;
g_auto(GStrv) tmp = NULL;
virCheckFlags(0, NULL);
tmp = g_new0(char *, VIR_DOMAIN_EVENT_ID_LAST + 1);
for (i = 0; i < VIR_DOMAIN_EVENT_ID_LAST; i++)
tmp[i] = g_strdup(virshDomainEventCallbacks[i].name);
return g_steal_pointer(&tmp);
}
static const vshCmdInfo info_event = {
.help = N_("Domain Events"),
.desc = N_("List event types, or wait for domain events to occur"),
};
static const vshCmdOptDef opts_event[] = {
{.name = "domain",
.type = VSH_OT_STRING,
.unwanted_positional = true,
.help = N_("filter by domain name, id or uuid"),
.completer = virshDomainNameCompleter,
},
{.name = "event",
.type = VSH_OT_STRING,
.unwanted_positional = true,
.completer = virshDomainEventNameCompleter,
.help = N_("which event type to wait for")
},
{.name = "all",
.type = VSH_OT_BOOL,
.help = N_("wait for all events instead of just one type")
},
{.name = "loop",
.type = VSH_OT_BOOL,
.help = N_("loop until timeout or interrupt, rather than one-shot")
},
{.name = "timeout",
.type = VSH_OT_INT,
.unwanted_positional = true,
.help = N_("timeout seconds")
},
{.name = "list",
.type = VSH_OT_BOOL,
.help = N_("list valid event types")
},
{.name = "timestamp",
.type = VSH_OT_BOOL,
.help = N_("show timestamp for each printed event")
},
{.name = NULL}
};
static bool
cmdEvent(vshControl *ctl, const vshCmd *cmd)
{
g_autoptr(virshDomain) dom = NULL;
bool ret = false;
int timeout = 0;
g_autofree virshDomEventData *data = NULL;
size_t ndata = 0;
size_t i;
const char *eventName = NULL;
bool all = vshCommandOptBool(cmd, "all");
bool loop = vshCommandOptBool(cmd, "loop");
bool timestamp = vshCommandOptBool(cmd, "timestamp");
int count = 0;
virshControl *priv = ctl->privData;
VSH_EXCLUSIVE_OPTIONS("all", "event");
VSH_EXCLUSIVE_OPTIONS("list", "all");
VSH_EXCLUSIVE_OPTIONS("list", "event");
if (vshCommandOptBool(cmd, "list")) {
for (i = 0; i < G_N_ELEMENTS(virshDomainEventCallbacks); i++)
vshPrint(ctl, "%s\n", virshDomainEventCallbacks[i].name);
return true;
}
if (vshCommandOptString(ctl, cmd, "event", &eventName) < 0)
return false;
if (!eventName && !all) {
vshError(ctl, "%s",
_("one of --list, --all, or --event is required"));
return false;
}
data = g_new0(virshDomEventData, G_N_ELEMENTS(virshDomainEventCallbacks));
for (i = 0; i < G_N_ELEMENTS(virshDomainEventCallbacks); i++) {
if (eventName &&
STRNEQ(eventName, virshDomainEventCallbacks[i].name))
continue;
data[ndata].event = i;
data[ndata].ctl = ctl;
data[ndata].loop = loop;
data[ndata].count = &count;
data[ndata].timestamp = timestamp;
data[ndata].cb = &virshDomainEventCallbacks[i];
data[ndata].id = -1;
ndata++;
}
if (ndata == 0) {
vshError(ctl, _("unknown event type %1$s"), eventName);
return false;
}
if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0)
goto cleanup;
if (vshCommandOptBool(cmd, "domain")) {
if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
goto cleanup;
}
if (vshEventStart(ctl, timeout) < 0)
goto cleanup;
for (i = 0; i < ndata; i++) {
if ((data[i].id = virConnectDomainEventRegisterAny(priv->conn, dom,
data[i].event,
data[i].cb->cb,
&data[i],
NULL)) < 0) {
/* When registering for all events: if the first
* registration succeeds, silently ignore failures on all
* later registrations on the assumption that the server
* is older and didn't know quite as many events. */
if (i)
vshResetLibvirtError();
else
goto cleanup;
}
}
switch (vshEventWait(ctl)) {
case VSH_EVENT_INTERRUPT:
vshPrint(ctl, "%s", _("event loop interrupted\n"));
break;
case VSH_EVENT_TIMEOUT:
vshPrint(ctl, "%s", _("event loop timed out\n"));
break;
case VSH_EVENT_DONE:
break;
default:
goto cleanup;
}
vshPrint(ctl, _("events received: %1$d\n"), count);
if (count)
ret = true;
cleanup:
vshEventCleanup(ctl);
if (data) {
for (i = 0; i < ndata; i++) {
if (data[i].id >= 0 &&
virConnectDomainEventDeregisterAny(priv->conn, data[i].id) < 0)
ret = false;
}
}
return ret;
}
const vshCmdDef domEventCmds[] = {
{.name = "event",
.handler = cmdEvent,
.opts = opts_event,
.info = &info_event,
.flags = 0
},
{.name = NULL}
};