On 02.06.2011 14:55, Daniel Veillard wrote: > On Thu, Jun 02, 2011 at 01:26:22PM +0200, Michal Privoznik wrote: >> * tools/virsh.c: Add screenshot command >> * tools/virsh.pod: Document new command >> * src/libvirt.c: Fix off-be-one error >> --- >> diff to v1: >> - make filename optional and generate filename when missing >> >> diff to v2: >> - Eric's review suggestions included >> >> src/libvirt.c | 2 +- >> tools/virsh.c | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++--- >> tools/virsh.pod | 9 +++ >> 3 files changed, 186 insertions(+), 12 deletions(-) >> >> diff --git a/src/libvirt.c b/src/libvirt.c >> index ee5c7cd..eaae0ec 100644 >> --- a/src/libvirt.c >> +++ b/src/libvirt.c >> @@ -2464,7 +2464,7 @@ error: >> * The screen ID is the sequential number of screen. In case of multiple >> * graphics cards, heads are enumerated before devices, e.g. having >> * two graphics cards, both with four heads, screen ID 5 addresses >> - * the first head on the second card. >> + * the second head on the second card. >> * >> * Returns a string representing the mime-type of the image format, or >> * NULL upon error. The caller must free() the returned value. >> diff --git a/tools/virsh.c b/tools/virsh.c >> index dfd5bd2..da10a0b 100644 >> --- a/tools/virsh.c >> +++ b/tools/virsh.c >> @@ -264,6 +264,9 @@ static bool vshCmdGrpHelp(vshControl *ctl, const char *name); >> static vshCmdOpt *vshCommandOpt(const vshCmd *cmd, const char *name); >> static int vshCommandOptInt(const vshCmd *cmd, const char *name, int *value) >> ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK; >> +static int vshCommandOptUInt(const vshCmd *cmd, const char *name, >> + unsigned int *value) >> + ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK; >> static int vshCommandOptUL(const vshCmd *cmd, const char *name, >> unsigned long *value) >> ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK; >> @@ -1938,6 +1941,153 @@ cmdDump(vshControl *ctl, const vshCmd *cmd) >> return ret; >> } >> >> +static const vshCmdInfo info_screenshot[] = { >> + {"help", N_("take a screenshot of a current domain console and store it " >> + "into a file")}, >> + {"desc", N_("screenshot of a current domain console")}, >> + {NULL, NULL} >> +}; >> + >> +static const vshCmdOptDef opts_screenshot[] = { >> + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, >> + {"file", VSH_OT_DATA, VSH_OFLAG_NONE, N_("where to store the screenshot")}, >> + {"screen", VSH_OT_INT, VSH_OFLAG_NONE, N_("ID of a screen to take screenshot of")}, >> + {NULL, 0, 0, NULL} >> +}; >> + >> +static int vshStreamSink(virStreamPtr st ATTRIBUTE_UNUSED, >> + const char *bytes, size_t nbytes, void *opaque) >> +{ >> + int *fd = opaque; >> + >> + return safewrite(*fd, bytes, nbytes); >> +} >> + >> +/** >> + * Generate string: '<domain name>-<timestamp>[<extension>]' >> + */ >> +static char * >> +vshGenFileName(vshControl *ctl, virDomainPtr dom, const char *mime) >> +{ >> + char timestr[100]; >> + struct timeval cur_time; >> + struct tm time_info; >> + const char *ext = NULL; >> + char *ret = NULL; >> + >> + /* We should be already connected, but doesn't >> + * hurt to check */ >> + if (!vshConnectionUsability(ctl, ctl->conn)) >> + return NULL; >> + >> + if (!dom) { >> + vshError(ctl, "%s", _("Invalid domain supplied")); >> + return NULL; >> + } >> + >> + if (STREQ(mime, "image/x-portable-pixmap")) >> + ext = ".ppm"; >> + else if (STREQ(mime, "image/png")) >> + ext = ".png"; >> + /* add mime type here */ >> + >> + gettimeofday(&cur_time, NULL); >> + localtime_r(&cur_time.tv_sec, &time_info); >> + strftime(timestr, sizeof(timestr), "%Y-%m-%d-%H:%M:%S", &time_info); >> + >> + if (virAsprintf(&ret, "%s-%s%s", virDomainGetName(dom), >> + timestr, ext ? ext : "") < 0) { >> + vshError(ctl, "%s", _("Out of memory")); >> + return NULL; >> + } >> + >> + return ret; >> +} >> + >> +static bool >> +cmdScreenshot(vshControl *ctl, const vshCmd *cmd) >> +{ >> + virDomainPtr dom; >> + const char *name = NULL; >> + char *file = NULL; >> + int fd = -1; >> + virStreamPtr st = NULL; >> + unsigned int screen = 0; >> + unsigned int flags = 0; /* currently unused */ >> + int ret = false; >> + bool created = true; >> + bool generated = false; >> + char *mime = NULL; >> + >> + if (!vshConnectionUsability(ctl, ctl->conn)) >> + return false; >> + >> + if (vshCommandOptString(cmd, "file", (const char **) &file) < 0) { >> + vshError(ctl, "%s", _("file must not be empty")); >> + return false; >> + } >> + >> + if (vshCommandOptUInt(cmd, "screen", &screen) < 0) { >> + vshError(ctl, "%s", _("invalid screen ID")); >> + return false; >> + } >> + >> + if (!(dom = vshCommandOptDomain(ctl, cmd, &name))) >> + return false; >> + >> + st = virStreamNew(ctl->conn, 0); >> + >> + mime = virDomainScreenshot(dom, st, screen, flags); >> + if (!mime) { >> + vshError(ctl, _("could not take a screenshot of %s"), name); >> + goto cleanup; >> + } >> + >> + if (!file) { >> + if (!(file=vshGenFileName(ctl, dom, mime))) >> + return false; >> + generated = true; >> + } >> + >> + if ((fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) { >> + created = false; >> + if (errno != EEXIST || >> + (fd = open(file, O_WRONLY|O_TRUNC, 0666)) < 0) { >> + vshError(ctl, _("cannot create file %s"), file); >> + goto cleanup; >> + } >> + } >> + >> + if (virStreamRecvAll(st, vshStreamSink, &fd) < 0) { >> + vshError(ctl, _("could not receive data from domain %s"), name); >> + goto cleanup; >> + } >> + >> + if (VIR_CLOSE(fd) < 0) { >> + vshError(ctl, _("cannot close file %s"), file); >> + goto cleanup; >> + } >> + >> + if (virStreamFinish(st) < 0) { >> + vshError(ctl, _("cannot close stream on domain %s"), name); >> + goto cleanup; >> + } >> + >> + vshPrint(ctl, _("Screenshot saved to %s, with type of %s"), file, mime); >> + ret = true; >> + >> +cleanup: >> + if (!ret && created) >> + unlink(file); >> + if (generated) >> + VIR_FREE(file); >> + virDomainFree(dom); >> + if (st) >> + virStreamFree(st); >> + VIR_FORCE_CLOSE(fd); >> + return ret; >> +} >> + >> /* >> * "resume" command >> */ >> @@ -7451,16 +7601,6 @@ static const vshCmdOptDef opts_vol_download[] = { >> {NULL, 0, 0, NULL} >> }; >> >> - >> -static int >> -cmdVolDownloadSink(virStreamPtr st ATTRIBUTE_UNUSED, >> - const char *bytes, size_t nbytes, void *opaque) >> -{ >> - int *fd = opaque; >> - >> - return safewrite(*fd, bytes, nbytes); >> -} >> - >> static bool >> cmdVolDownload (vshControl *ctl, const vshCmd *cmd) >> { >> @@ -7510,7 +7650,7 @@ cmdVolDownload (vshControl *ctl, const vshCmd *cmd) >> goto cleanup; >> } >> >> - if (virStreamRecvAll(st, cmdVolDownloadSink, &fd) < 0) { >> + if (virStreamRecvAll(st, vshStreamSink, &fd) < 0) { >> vshError(ctl, _("cannot receive data from volume %s"), name); >> goto cleanup; >> } >> @@ -10945,6 +11085,7 @@ static const vshCmdDef domManagementCmds[] = { >> {"resume", cmdResume, opts_resume, info_resume, 0}, >> {"save", cmdSave, opts_save, info_save, 0}, >> {"schedinfo", cmdSchedinfo, opts_schedinfo, info_schedinfo, 0}, >> + {"screenshot", cmdScreenshot, opts_screenshot, info_screenshot, 0}, >> {"setmaxmem", cmdSetmaxmem, opts_setmaxmem, info_setmaxmem, 0}, >> {"setmem", cmdSetmem, opts_setmem, info_setmem, 0}, >> {"setvcpus", cmdSetvcpus, opts_setvcpus, info_setvcpus, 0}, >> @@ -11527,6 +11668,30 @@ vshCommandOptInt(const vshCmd *cmd, const char *name, int *value) >> return ret; >> } >> >> + >> +/* >> + * Convert option to unsigned int >> + * See vshCommandOptInt() >> + */ >> +static int >> +vshCommandOptUInt(const vshCmd *cmd, const char *name, unsigned int *value) >> +{ >> + vshCmdOpt *arg = vshCommandOpt(cmd, name); >> + unsigned int ret = 0, num; >> + char *end_p = NULL; >> + >> + if ((arg != NULL) && (arg->data != NULL)) { >> + num = strtoul(arg->data, &end_p, 10); >> + ret = -1; >> + if ((arg->data != end_p) && (*end_p == 0)) { >> + *value = num; >> + ret = 1; >> + } >> + } >> + return ret; >> +} >> + >> + >> /* >> * Convert option to unsigned long >> * See vshCommandOptInt() >> diff --git a/tools/virsh.pod b/tools/virsh.pod >> index 9251db6..e4a11d5 100644 >> --- a/tools/virsh.pod >> +++ b/tools/virsh.pod >> @@ -596,6 +596,15 @@ Therefore, -1 is a useful shorthand for 262144. >> B<Note>: The weight and cap parameters are defined only for the >> XEN_CREDIT scheduler and are now I<DEPRECATED>. >> >> +=item B<screenshot> I<domain-id> optional I<imagefilepath> I<--screen> B<screenID> >> + >> +Takes a screenshot of a current domain console and stores it into a file. >> +Optionally, if hypervisor supports more displays for a domain, I<screenID> >> +allows to specify which screen will be captured. It is the sequential number >> +of screen. In case of multiple graphics cards, heads are enumerated before >> +devices, e.g. having two graphics cards, both with four heads, screen ID 5 >> +addresses the second head on the second card. >> + >> =item B<setmem> I<domain-id> B<kilobytes> optional I<--config> I<--live> >> I<--current> > > it's a bit late but it doesn't touch the API itself (except for fixing > the comment) and I think it would be good to have the virsh command > along with the API in 0.9.2, > > ACK, > > Daniel > Thanks, pushed. Michal -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list