xref: /openbmc/qemu/ui/ui-qmp-cmds.c (revision 7d87775f)
1 /*
2  * QMP commands related to UI
3  *
4  * Copyright IBM, Corp. 2011
5  *
6  * Authors:
7  *  Anthony Liguori   <aliguori@us.ibm.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2.  See
10  * the COPYING file in the top-level directory.
11  *
12  * Contributions after 2012-01-13 are licensed under the terms of the
13  * GNU GPL, version 2 or (at your option) any later version.
14  */
15 
16 #include "qemu/osdep.h"
17 
18 #include "io/channel-file.h"
19 #include "monitor/qmp-helpers.h"
20 #include "qapi/qapi-commands-ui.h"
21 #include "qapi/qmp/qerror.h"
22 #include "qemu/coroutine.h"
23 #include "qemu/cutils.h"
24 #include "trace.h"
25 #include "ui/console.h"
26 #include "ui/dbus-display.h"
27 #include "ui/qemu-spice.h"
28 #ifdef CONFIG_PNG
29 #include <png.h>
30 #endif
31 
32 void qmp_set_password(SetPasswordOptions *opts, Error **errp)
33 {
34     int rc;
35 
36     if (opts->protocol == DISPLAY_PROTOCOL_SPICE) {
37         if (!qemu_using_spice(errp)) {
38             return;
39         }
40         rc = qemu_spice.set_passwd(opts->password,
41                 opts->connected == SET_PASSWORD_ACTION_FAIL,
42                 opts->connected == SET_PASSWORD_ACTION_DISCONNECT);
43     } else {
44         assert(opts->protocol == DISPLAY_PROTOCOL_VNC);
45         if (opts->connected != SET_PASSWORD_ACTION_KEEP) {
46             /* vnc supports "connected=keep" only */
47             error_setg(errp, "parameter 'connected' must be 'keep'"
48                        " when 'protocol' is 'vnc'");
49             return;
50         }
51         /*
52          * Note that setting an empty password will not disable login
53          * through this interface.
54          */
55         rc = vnc_display_password(opts->u.vnc.display, opts->password);
56     }
57 
58     if (rc != 0) {
59         error_setg(errp, "Could not set password");
60     }
61 }
62 
63 void qmp_expire_password(ExpirePasswordOptions *opts, Error **errp)
64 {
65     time_t when;
66     int rc;
67     const char *whenstr = opts->time;
68     const char *numstr = NULL;
69     uint64_t num;
70 
71     if (strcmp(whenstr, "now") == 0) {
72         when = 0;
73     } else if (strcmp(whenstr, "never") == 0) {
74         when = TIME_MAX;
75     } else if (whenstr[0] == '+') {
76         when = time(NULL);
77         numstr = whenstr + 1;
78     } else {
79         when = 0;
80         numstr = whenstr;
81     }
82 
83     if (numstr) {
84         if (qemu_strtou64(numstr, NULL, 10, &num) < 0) {
85             error_setg(errp, "Parameter 'time' doesn't take value '%s'",
86                        whenstr);
87             return;
88         }
89         when += num;
90     }
91 
92     if (opts->protocol == DISPLAY_PROTOCOL_SPICE) {
93         if (!qemu_using_spice(errp)) {
94             return;
95         }
96         rc = qemu_spice.set_pw_expire(when);
97     } else {
98         assert(opts->protocol == DISPLAY_PROTOCOL_VNC);
99         rc = vnc_display_pw_expire(opts->u.vnc.display, when);
100     }
101 
102     if (rc != 0) {
103         error_setg(errp, "Could not set password expire time");
104     }
105 }
106 
107 #ifdef CONFIG_VNC
108 void qmp_change_vnc_password(const char *password, Error **errp)
109 {
110     if (vnc_display_password(NULL, password) < 0) {
111         error_setg(errp, "Could not set password");
112     }
113 }
114 #endif
115 
116 bool qmp_add_client_spice(int fd, bool has_skipauth, bool skipauth,
117                           bool has_tls, bool tls, Error **errp)
118 {
119     if (!qemu_using_spice(errp)) {
120         return false;
121     }
122     skipauth = has_skipauth ? skipauth : false;
123     tls = has_tls ? tls : false;
124     if (qemu_spice.display_add_client(fd, skipauth, tls) < 0) {
125         error_setg(errp, "spice failed to add client");
126         return false;
127     }
128     return true;
129 }
130 
131 #ifdef CONFIG_VNC
132 bool qmp_add_client_vnc(int fd, bool has_skipauth, bool skipauth,
133                         bool has_tls, bool tls, Error **errp)
134 {
135     skipauth = has_skipauth ? skipauth : false;
136     vnc_display_add_client(NULL, fd, skipauth);
137     return true;
138 }
139 #endif
140 
141 #ifdef CONFIG_DBUS_DISPLAY
142 bool qmp_add_client_dbus_display(int fd, bool has_skipauth, bool skipauth,
143                                  bool has_tls, bool tls, Error **errp)
144 {
145     if (!qemu_using_dbus_display(errp)) {
146         return false;
147     }
148     if (!qemu_dbus_display.add_client(fd, errp)) {
149         return false;
150     }
151     return true;
152 }
153 #endif
154 
155 void qmp_display_reload(DisplayReloadOptions *arg, Error **errp)
156 {
157     switch (arg->type) {
158     case DISPLAY_RELOAD_TYPE_VNC:
159 #ifdef CONFIG_VNC
160         if (arg->u.vnc.has_tls_certs && arg->u.vnc.tls_certs) {
161             vnc_display_reload_certs(NULL, errp);
162         }
163 #else
164         error_setg(errp, "vnc is invalid, missing 'CONFIG_VNC'");
165 #endif
166         break;
167     default:
168         abort();
169     }
170 }
171 
172 void qmp_display_update(DisplayUpdateOptions *arg, Error **errp)
173 {
174     switch (arg->type) {
175     case DISPLAY_UPDATE_TYPE_VNC:
176 #ifdef CONFIG_VNC
177         vnc_display_update(&arg->u.vnc, errp);
178 #else
179         error_setg(errp, "vnc is invalid, missing 'CONFIG_VNC'");
180 #endif
181         break;
182     default:
183         abort();
184     }
185 }
186 
187 void qmp_client_migrate_info(const char *protocol, const char *hostname,
188                              bool has_port, int64_t port,
189                              bool has_tls_port, int64_t tls_port,
190                              const char *cert_subject,
191                              Error **errp)
192 {
193     if (strcmp(protocol, "spice") == 0) {
194         if (!qemu_using_spice(errp)) {
195             return;
196         }
197 
198         if (!has_port && !has_tls_port) {
199             error_setg(errp, "parameter 'port' or 'tls-port' is required");
200             return;
201         }
202 
203         if (qemu_spice.migrate_info(hostname,
204                                     has_port ? port : -1,
205                                     has_tls_port ? tls_port : -1,
206                                     cert_subject)) {
207             error_setg(errp, "Could not set up display for migration");
208             return;
209         }
210         return;
211     }
212 
213     error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "protocol", "'spice'");
214 }
215 
216 #ifdef CONFIG_PIXMAN
217 #ifdef CONFIG_PNG
218 /**
219  * png_save: Take a screenshot as PNG
220  *
221  * Saves screendump as a PNG file
222  *
223  * Returns true for success or false for error.
224  *
225  * @fd: File descriptor for PNG file.
226  * @image: Image data in pixman format.
227  * @errp: Pointer to an error.
228  */
229 static bool png_save(int fd, pixman_image_t *image, Error **errp)
230 {
231     int width = pixman_image_get_width(image);
232     int height = pixman_image_get_height(image);
233     png_struct *png_ptr;
234     png_info *info_ptr;
235     g_autoptr(pixman_image_t) linebuf =
236         qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, width);
237     uint8_t *buf = (uint8_t *)pixman_image_get_data(linebuf);
238     FILE *f = fdopen(fd, "wb");
239     int y;
240     if (!f) {
241         error_setg_errno(errp, errno,
242                          "Failed to create file from file descriptor");
243         return false;
244     }
245 
246     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
247                                       NULL, NULL);
248     if (!png_ptr) {
249         error_setg(errp, "PNG creation failed. Unable to write struct");
250         fclose(f);
251         return false;
252     }
253 
254     info_ptr = png_create_info_struct(png_ptr);
255 
256     if (!info_ptr) {
257         error_setg(errp, "PNG creation failed. Unable to write info");
258         fclose(f);
259         png_destroy_write_struct(&png_ptr, &info_ptr);
260         return false;
261     }
262 
263     png_init_io(png_ptr, f);
264 
265     png_set_IHDR(png_ptr, info_ptr, width, height, 8,
266                  PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
267                  PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
268 
269     png_write_info(png_ptr, info_ptr);
270 
271     for (y = 0; y < height; ++y) {
272         qemu_pixman_linebuf_fill(linebuf, image, width, 0, y);
273         png_write_row(png_ptr, buf);
274     }
275 
276     png_write_end(png_ptr, NULL);
277 
278     png_destroy_write_struct(&png_ptr, &info_ptr);
279 
280     if (fclose(f) != 0) {
281         error_setg_errno(errp, errno,
282                          "PNG creation failed. Unable to close file");
283         return false;
284     }
285 
286     return true;
287 }
288 
289 #else /* no png support */
290 
291 static bool png_save(int fd, pixman_image_t *image, Error **errp)
292 {
293     error_setg(errp, "Enable PNG support with libpng for screendump");
294     return false;
295 }
296 
297 #endif /* CONFIG_PNG */
298 
299 static bool ppm_save(int fd, pixman_image_t *image, Error **errp)
300 {
301     int width = pixman_image_get_width(image);
302     int height = pixman_image_get_height(image);
303     g_autoptr(Object) ioc = OBJECT(qio_channel_file_new_fd(fd));
304     g_autofree char *header = NULL;
305     g_autoptr(pixman_image_t) linebuf = NULL;
306     int y;
307 
308     trace_ppm_save(fd, image);
309 
310     header = g_strdup_printf("P6\n%d %d\n%d\n", width, height, 255);
311     if (qio_channel_write_all(QIO_CHANNEL(ioc),
312                               header, strlen(header), errp) < 0) {
313         return false;
314     }
315 
316     linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, width);
317     for (y = 0; y < height; y++) {
318         qemu_pixman_linebuf_fill(linebuf, image, width, 0, y);
319         if (qio_channel_write_all(QIO_CHANNEL(ioc),
320                                   (char *)pixman_image_get_data(linebuf),
321                                   pixman_image_get_stride(linebuf), errp) < 0) {
322             return false;
323         }
324     }
325 
326     return true;
327 }
328 
329 /* Safety: coroutine-only, concurrent-coroutine safe, main thread only */
330 void coroutine_fn
331 qmp_screendump(const char *filename, const char *device,
332                bool has_head, int64_t head,
333                bool has_format, ImageFormat format, Error **errp)
334 {
335     g_autoptr(pixman_image_t) image = NULL;
336     QemuConsole *con;
337     DisplaySurface *surface;
338     int fd;
339 
340     if (device) {
341         con = qemu_console_lookup_by_device_name(device, has_head ? head : 0,
342                                                  errp);
343         if (!con) {
344             return;
345         }
346     } else {
347         if (has_head) {
348             error_setg(errp, "'head' must be specified together with 'device'");
349             return;
350         }
351         con = qemu_console_lookup_by_index(0);
352         if (!con) {
353             error_setg(errp, "There is no console to take a screendump from");
354             return;
355         }
356     }
357 
358     qemu_console_co_wait_update(con);
359 
360     /*
361      * All pending coroutines are woken up, while the BQL is held.  No
362      * further graphic update are possible until it is released.  Take
363      * an image ref before that.
364      */
365     surface = qemu_console_surface(con);
366     if (!surface) {
367         error_setg(errp, "no surface");
368         return;
369     }
370     image = pixman_image_ref(surface->image);
371 
372     fd = qemu_open_old(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
373     if (fd == -1) {
374         error_setg(errp, "failed to open file '%s': %s", filename,
375                    strerror(errno));
376         return;
377     }
378 
379     /*
380      * The image content could potentially be updated as the coroutine
381      * yields and releases the BQL. It could produce corrupted dump, but
382      * it should be otherwise safe.
383      */
384     if (has_format && format == IMAGE_FORMAT_PNG) {
385         /* PNG format specified for screendump */
386         if (!png_save(fd, image, errp)) {
387             qemu_unlink(filename);
388         }
389     } else {
390         /* PPM format specified/default for screendump */
391         if (!ppm_save(fd, image, errp)) {
392             qemu_unlink(filename);
393         }
394     }
395 }
396 #endif /* CONFIG_PIXMAN */
397