mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-22 05:35:25 +00:00
100cbccecd
Historically the command parser in virsh parses/fills even optional arguments with values as if they were positional unless opted out using VSH_OFLAG_REQ_OPT. This creates unexpected situations when commands can break in this unwanted semantics: $ virsh snapshot-create-as --print-xml 1 2 3 <domainsnapshot> <name>2</name> <description>3</description> </domainsnapshot> To prevent any further addition annotate the rest of the arguments with the 'unwanted_positional' flag, so that the parser can keep parsing them as such but any further optional argument will not have this behaviour. Signed-off-by: Peter Krempa <pkrempa@redhat.com> Reviewed-by: Ján Tomko <jtomko@redhat.com>
1026 lines
34 KiB
C
1026 lines
34 KiB
C
/*
|
|
* 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
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#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 (vshCommandOptStringReq(ctl, cmd, "event", &eventName) < 0)
|
|
return false;
|
|
|
|
if (!eventName && !all) {
|
|
vshError(ctl, "%s",
|
|
_("one of --list, --all, or --event <type> 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}
|
|
};
|