1 |
|
|
/* $OpenBSD: vmctl.c,v 1.44 2017/09/08 07:08:49 mlarkin Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2014 Mike Larkin <mlarkin@openbsd.org> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/queue.h> |
20 |
|
|
#include <sys/uio.h> |
21 |
|
|
#include <sys/stat.h> |
22 |
|
|
#include <sys/socket.h> |
23 |
|
|
#include <sys/un.h> |
24 |
|
|
|
25 |
|
|
#include <machine/vmmvar.h> |
26 |
|
|
|
27 |
|
|
#include <ctype.h> |
28 |
|
|
#include <err.h> |
29 |
|
|
#include <errno.h> |
30 |
|
|
#include <fcntl.h> |
31 |
|
|
#include <imsg.h> |
32 |
|
|
#include <limits.h> |
33 |
|
|
#include <stdio.h> |
34 |
|
|
#include <stdlib.h> |
35 |
|
|
#include <string.h> |
36 |
|
|
#include <unistd.h> |
37 |
|
|
#include <util.h> |
38 |
|
|
#include <pwd.h> |
39 |
|
|
#include <grp.h> |
40 |
|
|
|
41 |
|
|
#include "vmd.h" |
42 |
|
|
#include "vmctl.h" |
43 |
|
|
#include "atomicio.h" |
44 |
|
|
|
45 |
|
|
extern char *__progname; |
46 |
|
|
uint32_t info_id; |
47 |
|
|
char info_name[VMM_MAX_NAME_LEN]; |
48 |
|
|
int info_console; |
49 |
|
|
|
50 |
|
|
/* |
51 |
|
|
* vm_start |
52 |
|
|
* |
53 |
|
|
* Request vmd to start the VM defined by the supplied parameters |
54 |
|
|
* |
55 |
|
|
* Parameters: |
56 |
|
|
* start_id: optional ID of the VM |
57 |
|
|
* name: optional name of the VM |
58 |
|
|
* memsize: memory size (MB) of the VM to create |
59 |
|
|
* nnics: number of vionet network interfaces to create |
60 |
|
|
* nics: switch names of the network interfaces to create |
61 |
|
|
* ndisks: number of disk images |
62 |
|
|
* disks: disk image file names |
63 |
|
|
* kernel: kernel image to load |
64 |
|
|
* |
65 |
|
|
* Return: |
66 |
|
|
* 0 if the request to start the VM was sent successfully. |
67 |
|
|
* ENOMEM if a memory allocation failure occurred. |
68 |
|
|
*/ |
69 |
|
|
int |
70 |
|
|
vm_start(uint32_t start_id, const char *name, int memsize, int nnics, |
71 |
|
|
char **nics, int ndisks, char **disks, char *kernel) |
72 |
|
|
{ |
73 |
|
|
struct vmop_create_params *vmc; |
74 |
|
|
struct vm_create_params *vcp; |
75 |
|
|
unsigned int flags = 0; |
76 |
|
|
int i; |
77 |
|
|
const char *s; |
78 |
|
|
|
79 |
|
|
if (memsize) |
80 |
|
|
flags |= VMOP_CREATE_MEMORY; |
81 |
|
|
if (nnics) |
82 |
|
|
flags |= VMOP_CREATE_NETWORK; |
83 |
|
|
if (ndisks) |
84 |
|
|
flags |= VMOP_CREATE_DISK; |
85 |
|
|
if (kernel) |
86 |
|
|
flags |= VMOP_CREATE_KERNEL; |
87 |
|
|
if (flags != 0) { |
88 |
|
|
if (memsize < 1) |
89 |
|
|
memsize = VM_DEFAULT_MEMORY; |
90 |
|
|
if (ndisks > VMM_MAX_DISKS_PER_VM) |
91 |
|
|
errx(1, "too many disks"); |
92 |
|
|
else if (ndisks == 0) |
93 |
|
|
warnx("starting without disks"); |
94 |
|
|
if (kernel == NULL && ndisks == 0) |
95 |
|
|
errx(1, "no kernel or disk specified"); |
96 |
|
|
if (nnics == -1) |
97 |
|
|
nnics = 0; |
98 |
|
|
if (nnics > VMM_MAX_NICS_PER_VM) |
99 |
|
|
errx(1, "too many network interfaces"); |
100 |
|
|
if (nnics == 0) |
101 |
|
|
warnx("starting without network interfaces"); |
102 |
|
|
} |
103 |
|
|
|
104 |
|
|
vmc = calloc(1, sizeof(struct vmop_create_params)); |
105 |
|
|
if (vmc == NULL) |
106 |
|
|
return (ENOMEM); |
107 |
|
|
|
108 |
|
|
vmc->vmc_flags = flags; |
109 |
|
|
|
110 |
|
|
/* vcp includes configuration that is shared with the kernel */ |
111 |
|
|
vcp = &vmc->vmc_params; |
112 |
|
|
|
113 |
|
|
/* |
114 |
|
|
* XXX: vmd(8) fills in the actual memory ranges. vmctl(8) |
115 |
|
|
* just passes in the actual memory size in MB here. |
116 |
|
|
*/ |
117 |
|
|
vcp->vcp_nmemranges = 1; |
118 |
|
|
vcp->vcp_memranges[0].vmr_size = memsize; |
119 |
|
|
|
120 |
|
|
vcp->vcp_ncpus = 1; |
121 |
|
|
vcp->vcp_ndisks = ndisks; |
122 |
|
|
vcp->vcp_nnics = nnics; |
123 |
|
|
vcp->vcp_id = start_id; |
124 |
|
|
|
125 |
|
|
for (i = 0 ; i < ndisks; i++) |
126 |
|
|
strlcpy(vcp->vcp_disks[i], disks[i], VMM_MAX_PATH_DISK); |
127 |
|
|
for (i = 0 ; i < nnics; i++) { |
128 |
|
|
vmc->vmc_ifflags[i] = VMIFF_UP; |
129 |
|
|
|
130 |
|
|
if (strcmp(".", nics[i]) == 0) { |
131 |
|
|
/* Add a "local" interface */ |
132 |
|
|
strlcpy(vmc->vmc_ifswitch[i], "", IF_NAMESIZE); |
133 |
|
|
vmc->vmc_ifflags[i] |= VMIFF_LOCAL; |
134 |
|
|
} else { |
135 |
|
|
/* Add an interface to a switch */ |
136 |
|
|
strlcpy(vmc->vmc_ifswitch[i], nics[i], IF_NAMESIZE); |
137 |
|
|
} |
138 |
|
|
} |
139 |
|
|
if (name != NULL) { |
140 |
|
|
/* Allow VMs names with alphanumeric characters, dot, hyphen |
141 |
|
|
* and underscore. But disallow dot, hyphen and underscore at |
142 |
|
|
* the start. |
143 |
|
|
*/ |
144 |
|
|
if (*name == '-' || *name == '.' || *name == '_') |
145 |
|
|
errx(1, "Invalid VM name"); |
146 |
|
|
|
147 |
|
|
for (s = name; *s != '\0'; ++s) { |
148 |
|
|
if (!(isalnum(*s) || *s == '.' || *s == '-' || |
149 |
|
|
*s == '_')) |
150 |
|
|
errx(1, "Invalid VM name"); |
151 |
|
|
} |
152 |
|
|
|
153 |
|
|
strlcpy(vcp->vcp_name, name, VMM_MAX_NAME_LEN); |
154 |
|
|
} |
155 |
|
|
if (kernel != NULL) |
156 |
|
|
strlcpy(vcp->vcp_kernel, kernel, VMM_MAX_KERNEL_PATH); |
157 |
|
|
|
158 |
|
|
imsg_compose(ibuf, IMSG_VMDOP_START_VM_REQUEST, 0, 0, -1, |
159 |
|
|
vmc, sizeof(struct vmop_create_params)); |
160 |
|
|
|
161 |
|
|
free(vcp); |
162 |
|
|
return (0); |
163 |
|
|
} |
164 |
|
|
|
165 |
|
|
/* |
166 |
|
|
* vm_start_complete |
167 |
|
|
* |
168 |
|
|
* Callback function invoked when we are expecting an |
169 |
|
|
* IMSG_VMDOP_START_VMM_RESPONSE message indicating the completion of |
170 |
|
|
* a start vm operation. |
171 |
|
|
* |
172 |
|
|
* Parameters: |
173 |
|
|
* imsg : response imsg received from vmd |
174 |
|
|
* ret : return value |
175 |
|
|
* autoconnect : open the console after startup |
176 |
|
|
* |
177 |
|
|
* Return: |
178 |
|
|
* Always 1 to indicate we have processed the return message (even if it |
179 |
|
|
* was an incorrect/failure message) |
180 |
|
|
* |
181 |
|
|
* The function also sets 'ret' to the error code as follows: |
182 |
|
|
* 0 : Message successfully processed |
183 |
|
|
* EINVAL: Invalid or unexpected response from vmd |
184 |
|
|
* EIO : vm_start command failed |
185 |
|
|
* ENOENT: a specified component of the VM could not be found (disk image, |
186 |
|
|
* BIOS firmware image, etc) |
187 |
|
|
*/ |
188 |
|
|
int |
189 |
|
|
vm_start_complete(struct imsg *imsg, int *ret, int autoconnect) |
190 |
|
|
{ |
191 |
|
|
struct vmop_result *vmr; |
192 |
|
|
int res; |
193 |
|
|
|
194 |
|
|
if (imsg->hdr.type == IMSG_VMDOP_START_VM_RESPONSE) { |
195 |
|
|
vmr = (struct vmop_result *)imsg->data; |
196 |
|
|
res = vmr->vmr_result; |
197 |
|
|
if (res) { |
198 |
|
|
switch (res) { |
199 |
|
|
case VMD_BIOS_MISSING: |
200 |
|
|
warnx("vmm bios firmware file not found."); |
201 |
|
|
*ret = ENOENT; |
202 |
|
|
break; |
203 |
|
|
case VMD_DISK_MISSING: |
204 |
|
|
warnx("could not find specified disk image(s)"); |
205 |
|
|
*ret = ENOENT; |
206 |
|
|
break; |
207 |
|
|
case VMD_DISK_INVALID: |
208 |
|
|
warnx("specified disk image(s) are " |
209 |
|
|
"not regular files"); |
210 |
|
|
*ret = ENOENT; |
211 |
|
|
break; |
212 |
|
|
default: |
213 |
|
|
errno = res; |
214 |
|
|
warn("start vm command failed"); |
215 |
|
|
*ret = EIO; |
216 |
|
|
} |
217 |
|
|
} else if (autoconnect) { |
218 |
|
|
/* does not return */ |
219 |
|
|
ctl_openconsole(vmr->vmr_ttyname); |
220 |
|
|
} else { |
221 |
|
|
warnx("started vm %d successfully, tty %s", |
222 |
|
|
vmr->vmr_id, vmr->vmr_ttyname); |
223 |
|
|
*ret = 0; |
224 |
|
|
} |
225 |
|
|
} else { |
226 |
|
|
warnx("unexpected response received from vmd"); |
227 |
|
|
*ret = EINVAL; |
228 |
|
|
} |
229 |
|
|
|
230 |
|
|
return (1); |
231 |
|
|
} |
232 |
|
|
|
233 |
|
|
void |
234 |
|
|
send_vm(uint32_t id, const char *name) |
235 |
|
|
{ |
236 |
|
|
struct vmop_id vid; |
237 |
|
|
int fds[2], readn, writen; |
238 |
|
|
long pagesz; |
239 |
|
|
char *buf; |
240 |
|
|
|
241 |
|
|
pagesz = getpagesize(); |
242 |
|
|
buf = malloc(pagesz); |
243 |
|
|
if (buf == NULL) |
244 |
|
|
errx(1, "%s: memory allocation failure", __func__); |
245 |
|
|
|
246 |
|
|
memset(&vid, 0, sizeof(vid)); |
247 |
|
|
vid.vid_id = id; |
248 |
|
|
if (name != NULL) |
249 |
|
|
strlcpy(vid.vid_name, name, sizeof(vid.vid_name)); |
250 |
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) == -1) { |
251 |
|
|
warnx("%s: socketpair creation failed", __func__); |
252 |
|
|
} else { |
253 |
|
|
imsg_compose(ibuf, IMSG_VMDOP_SEND_VM_REQUEST, 0, 0, fds[0], |
254 |
|
|
&vid, sizeof(vid)); |
255 |
|
|
imsg_flush(ibuf); |
256 |
|
|
while (1) { |
257 |
|
|
readn = atomicio(read, fds[1], buf, pagesz); |
258 |
|
|
if (!readn) |
259 |
|
|
break; |
260 |
|
|
writen = atomicio(vwrite, STDOUT_FILENO, buf, |
261 |
|
|
readn); |
262 |
|
|
if (writen != readn) |
263 |
|
|
break; |
264 |
|
|
} |
265 |
|
|
if (vid.vid_id) |
266 |
|
|
warnx("sent vm %d successfully", vid.vid_id); |
267 |
|
|
else |
268 |
|
|
warnx("sent vm %s successfully", vid.vid_name); |
269 |
|
|
} |
270 |
|
|
|
271 |
|
|
free(buf); |
272 |
|
|
} |
273 |
|
|
|
274 |
|
|
void |
275 |
|
|
vm_receive(uint32_t id, const char *name) |
276 |
|
|
{ |
277 |
|
|
struct vmop_id vid; |
278 |
|
|
int fds[2], readn, writen; |
279 |
|
|
long pagesz; |
280 |
|
|
char *buf; |
281 |
|
|
|
282 |
|
|
pagesz = getpagesize(); |
283 |
|
|
buf = malloc(pagesz); |
284 |
|
|
if (buf == NULL) |
285 |
|
|
errx(1, "%s: memory allocation failure", __func__); |
286 |
|
|
|
287 |
|
|
memset(&vid, 0, sizeof(vid)); |
288 |
|
|
if (name != NULL) |
289 |
|
|
strlcpy(vid.vid_name, name, sizeof(vid.vid_name)); |
290 |
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) == -1) { |
291 |
|
|
warnx("%s: socketpair creation failed", __func__); |
292 |
|
|
} else { |
293 |
|
|
imsg_compose(ibuf, IMSG_VMDOP_RECEIVE_VM_REQUEST, 0, 0, fds[0], |
294 |
|
|
&vid, sizeof(vid)); |
295 |
|
|
imsg_flush(ibuf); |
296 |
|
|
while (1) { |
297 |
|
|
readn = atomicio(read, STDIN_FILENO, buf, pagesz); |
298 |
|
|
if (!readn) { |
299 |
|
|
close(fds[1]); |
300 |
|
|
break; |
301 |
|
|
} |
302 |
|
|
writen = atomicio(vwrite, fds[1], buf, readn); |
303 |
|
|
if (writen != readn) |
304 |
|
|
break; |
305 |
|
|
} |
306 |
|
|
} |
307 |
|
|
|
308 |
|
|
free(buf); |
309 |
|
|
} |
310 |
|
|
|
311 |
|
|
void |
312 |
|
|
pause_vm(uint32_t pause_id, const char *name) |
313 |
|
|
{ |
314 |
|
|
struct vmop_id vid; |
315 |
|
|
|
316 |
|
|
memset(&vid, 0, sizeof(vid)); |
317 |
|
|
vid.vid_id = pause_id; |
318 |
|
|
if (name != NULL) |
319 |
|
|
(void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name)); |
320 |
|
|
|
321 |
|
|
imsg_compose(ibuf, IMSG_VMDOP_PAUSE_VM, 0, 0, -1, |
322 |
|
|
&vid, sizeof(vid)); |
323 |
|
|
} |
324 |
|
|
|
325 |
|
|
int |
326 |
|
|
pause_vm_complete(struct imsg *imsg, int *ret) |
327 |
|
|
{ |
328 |
|
|
struct vmop_result *vmr; |
329 |
|
|
int res; |
330 |
|
|
|
331 |
|
|
if (imsg->hdr.type == IMSG_VMDOP_PAUSE_VM_RESPONSE) { |
332 |
|
|
vmr = (struct vmop_result *)imsg->data; |
333 |
|
|
res = vmr->vmr_result; |
334 |
|
|
if (res) { |
335 |
|
|
errno = res; |
336 |
|
|
warn("pause vm command failed"); |
337 |
|
|
*ret = EIO; |
338 |
|
|
} else { |
339 |
|
|
warnx("paused vm %d successfully", vmr->vmr_id); |
340 |
|
|
*ret = 0; |
341 |
|
|
} |
342 |
|
|
} else { |
343 |
|
|
warnx("unexpected response received from vmd"); |
344 |
|
|
*ret = EINVAL; |
345 |
|
|
} |
346 |
|
|
|
347 |
|
|
return (1); |
348 |
|
|
} |
349 |
|
|
|
350 |
|
|
void |
351 |
|
|
unpause_vm(uint32_t pause_id, const char *name) |
352 |
|
|
{ |
353 |
|
|
struct vmop_id vid; |
354 |
|
|
|
355 |
|
|
memset(&vid, 0, sizeof(vid)); |
356 |
|
|
vid.vid_id = pause_id; |
357 |
|
|
if (name != NULL) |
358 |
|
|
(void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name)); |
359 |
|
|
|
360 |
|
|
imsg_compose(ibuf, IMSG_VMDOP_UNPAUSE_VM, 0, 0, -1, |
361 |
|
|
&vid, sizeof(vid)); |
362 |
|
|
} |
363 |
|
|
|
364 |
|
|
int |
365 |
|
|
unpause_vm_complete(struct imsg *imsg, int *ret) |
366 |
|
|
{ |
367 |
|
|
struct vmop_result *vmr; |
368 |
|
|
int res; |
369 |
|
|
|
370 |
|
|
if (imsg->hdr.type == IMSG_VMDOP_UNPAUSE_VM_RESPONSE) { |
371 |
|
|
vmr = (struct vmop_result *)imsg->data; |
372 |
|
|
res = vmr->vmr_result; |
373 |
|
|
if (res) { |
374 |
|
|
errno = res; |
375 |
|
|
warn("unpause vm command failed"); |
376 |
|
|
*ret = EIO; |
377 |
|
|
} else { |
378 |
|
|
warnx("unpaused vm %d successfully", vmr->vmr_id); |
379 |
|
|
*ret = 0; |
380 |
|
|
} |
381 |
|
|
} else { |
382 |
|
|
warnx("unexpected response received from vmd"); |
383 |
|
|
*ret = EINVAL; |
384 |
|
|
} |
385 |
|
|
|
386 |
|
|
return (1); |
387 |
|
|
} |
388 |
|
|
|
389 |
|
|
/* |
390 |
|
|
* terminate_vm |
391 |
|
|
* |
392 |
|
|
* Request vmd to stop the VM indicated |
393 |
|
|
* |
394 |
|
|
* Parameters: |
395 |
|
|
* terminate_id: ID of the vm to be terminated |
396 |
|
|
* name: optional name of the VM to be terminated |
397 |
|
|
*/ |
398 |
|
|
void |
399 |
|
|
terminate_vm(uint32_t terminate_id, const char *name) |
400 |
|
|
{ |
401 |
|
|
struct vmop_id vid; |
402 |
|
|
|
403 |
|
|
memset(&vid, 0, sizeof(vid)); |
404 |
|
|
vid.vid_id = terminate_id; |
405 |
|
|
if (name != NULL) |
406 |
|
|
(void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name)); |
407 |
|
|
|
408 |
|
|
imsg_compose(ibuf, IMSG_VMDOP_TERMINATE_VM_REQUEST, 0, 0, -1, |
409 |
|
|
&vid, sizeof(vid)); |
410 |
|
|
} |
411 |
|
|
|
412 |
|
|
/* |
413 |
|
|
* terminate_vm_complete |
414 |
|
|
* |
415 |
|
|
* Callback function invoked when we are expecting an |
416 |
|
|
* IMSG_VMDOP_TERMINATE_VMM_RESPONSE message indicating the completion of |
417 |
|
|
* a terminate vm operation. |
418 |
|
|
* |
419 |
|
|
* Parameters: |
420 |
|
|
* imsg : response imsg received from vmd |
421 |
|
|
* ret : return value |
422 |
|
|
* |
423 |
|
|
* Return: |
424 |
|
|
* Always 1 to indicate we have processed the return message (even if it |
425 |
|
|
* was an incorrect/failure message) |
426 |
|
|
* |
427 |
|
|
* The function also sets 'ret' to the error code as follows: |
428 |
|
|
* 0 : Message successfully processed |
429 |
|
|
* EINVAL: Invalid or unexpected response from vmd |
430 |
|
|
* EIO : terminate_vm command failed |
431 |
|
|
*/ |
432 |
|
|
int |
433 |
|
|
terminate_vm_complete(struct imsg *imsg, int *ret) |
434 |
|
|
{ |
435 |
|
|
struct vmop_result *vmr; |
436 |
|
|
int res; |
437 |
|
|
|
438 |
|
|
if (imsg->hdr.type == IMSG_VMDOP_TERMINATE_VM_RESPONSE) { |
439 |
|
|
vmr = (struct vmop_result *)imsg->data; |
440 |
|
|
res = vmr->vmr_result; |
441 |
|
|
if (res) { |
442 |
|
|
switch (res) { |
443 |
|
|
case VMD_VM_STOP_INVALID: |
444 |
|
|
warnx("cannot stop vm that is not running"); |
445 |
|
|
*ret = EINVAL; |
446 |
|
|
break; |
447 |
|
|
case ENOENT: |
448 |
|
|
warnx("vm not found"); |
449 |
|
|
*ret = EIO; |
450 |
|
|
break; |
451 |
|
|
default: |
452 |
|
|
warn("terminate vm command failed"); |
453 |
|
|
*ret = EIO; |
454 |
|
|
} |
455 |
|
|
} else { |
456 |
|
|
warnx("sent request to terminate vm %d", vmr->vmr_id); |
457 |
|
|
*ret = 0; |
458 |
|
|
} |
459 |
|
|
} else { |
460 |
|
|
warnx("unexpected response received from vmd"); |
461 |
|
|
*ret = EINVAL; |
462 |
|
|
} |
463 |
|
|
errno = *ret; |
464 |
|
|
|
465 |
|
|
return (1); |
466 |
|
|
} |
467 |
|
|
|
468 |
|
|
/* |
469 |
|
|
* get_info_vm |
470 |
|
|
* |
471 |
|
|
* Return the list of all running VMs or find a specific VM by ID or name. |
472 |
|
|
* |
473 |
|
|
* Parameters: |
474 |
|
|
* id: optional ID of a VM to list |
475 |
|
|
* name: optional name of a VM to list |
476 |
|
|
* console: if true, open the console of the selected VM (by name or ID) |
477 |
|
|
* |
478 |
|
|
* Request a list of running VMs from vmd |
479 |
|
|
*/ |
480 |
|
|
void |
481 |
|
|
get_info_vm(uint32_t id, const char *name, int console) |
482 |
|
|
{ |
483 |
|
|
info_id = id; |
484 |
|
|
if (name != NULL) |
485 |
|
|
(void)strlcpy(info_name, name, sizeof(info_name)); |
486 |
|
|
info_console = console; |
487 |
|
|
imsg_compose(ibuf, IMSG_VMDOP_GET_INFO_VM_REQUEST, 0, 0, -1, NULL, 0); |
488 |
|
|
} |
489 |
|
|
|
490 |
|
|
/* |
491 |
|
|
* check_info_id |
492 |
|
|
* |
493 |
|
|
* Check if requested name or ID of a VM matches specified arguments |
494 |
|
|
* |
495 |
|
|
* Parameters: |
496 |
|
|
* name: name of the VM |
497 |
|
|
* id: ID of the VM |
498 |
|
|
*/ |
499 |
|
|
int |
500 |
|
|
check_info_id(const char *name, uint32_t id) |
501 |
|
|
{ |
502 |
|
|
if (info_id == 0 && *info_name == '\0') |
503 |
|
|
return (-1); |
504 |
|
|
if (info_id != 0 && info_id == id) |
505 |
|
|
return (1); |
506 |
|
|
if (*info_name != '\0' && name && strcmp(info_name, name) == 0) |
507 |
|
|
return (1); |
508 |
|
|
return (0); |
509 |
|
|
} |
510 |
|
|
|
511 |
|
|
/* |
512 |
|
|
* add_info |
513 |
|
|
* |
514 |
|
|
* Callback function invoked when we are expecting an |
515 |
|
|
* IMSG_VMDOP_GET_INFO_VM_DATA message indicating the receipt of additional |
516 |
|
|
* "list vm" data, or an IMSG_VMDOP_GET_INFO_VM_END_DATA message indicating |
517 |
|
|
* that no additional "list vm" data will be forthcoming. |
518 |
|
|
* |
519 |
|
|
* Parameters: |
520 |
|
|
* imsg : response imsg received from vmd |
521 |
|
|
* ret : return value |
522 |
|
|
* |
523 |
|
|
* Return: |
524 |
|
|
* 0 : the returned data was successfully added to the "list vm" data. |
525 |
|
|
* The caller can expect more data. |
526 |
|
|
* 1 : IMSG_VMDOP_GET_INFO_VM_END_DATA received (caller should not call |
527 |
|
|
* add_info again), or an error occurred adding the returned data |
528 |
|
|
* to the "list vm" data. The caller should check the value of |
529 |
|
|
* 'ret' to determine which case occurred. |
530 |
|
|
* |
531 |
|
|
* This function does not return if a VM is found and info_console is set. |
532 |
|
|
* |
533 |
|
|
* The function also sets 'ret' to the error code as follows: |
534 |
|
|
* 0 : Message successfully processed |
535 |
|
|
* EINVAL: Invalid or unexpected response from vmd |
536 |
|
|
* ENOMEM: memory allocation failure |
537 |
|
|
*/ |
538 |
|
|
int |
539 |
|
|
add_info(struct imsg *imsg, int *ret) |
540 |
|
|
{ |
541 |
|
|
static size_t ct = 0; |
542 |
|
|
static struct vmop_info_result *vir = NULL; |
543 |
|
|
|
544 |
|
|
if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_DATA) { |
545 |
|
|
vir = reallocarray(vir, ct + 1, |
546 |
|
|
sizeof(struct vmop_info_result)); |
547 |
|
|
if (vir == NULL) { |
548 |
|
|
*ret = ENOMEM; |
549 |
|
|
return (1); |
550 |
|
|
} |
551 |
|
|
memcpy(&vir[ct], imsg->data, sizeof(struct vmop_info_result)); |
552 |
|
|
ct++; |
553 |
|
|
*ret = 0; |
554 |
|
|
return (0); |
555 |
|
|
} else if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_END_DATA) { |
556 |
|
|
if (info_console) |
557 |
|
|
vm_console(vir, ct); |
558 |
|
|
else |
559 |
|
|
print_vm_info(vir, ct); |
560 |
|
|
free(vir); |
561 |
|
|
*ret = 0; |
562 |
|
|
return (1); |
563 |
|
|
} else { |
564 |
|
|
*ret = EINVAL; |
565 |
|
|
return (1); |
566 |
|
|
} |
567 |
|
|
} |
568 |
|
|
|
569 |
|
|
/* |
570 |
|
|
* print_vm_info |
571 |
|
|
* |
572 |
|
|
* Prints the vm information returned from vmd in 'list' to stdout. |
573 |
|
|
* |
574 |
|
|
* Parameters |
575 |
|
|
* list: the vm information (consolidated) returned from vmd via imsg |
576 |
|
|
* ct : the size (number of elements in 'list') of the result |
577 |
|
|
*/ |
578 |
|
|
void |
579 |
|
|
print_vm_info(struct vmop_info_result *list, size_t ct) |
580 |
|
|
{ |
581 |
|
|
struct vm_info_result *vir; |
582 |
|
|
struct vmop_info_result *vmi; |
583 |
|
|
size_t i, j; |
584 |
|
|
char *vcpu_state, *tty; |
585 |
|
|
char curmem[FMT_SCALED_STRSIZE]; |
586 |
|
|
char maxmem[FMT_SCALED_STRSIZE]; |
587 |
|
|
char user[16], group[16]; |
588 |
|
|
struct passwd *pw; |
589 |
|
|
struct group *gr; |
590 |
|
|
|
591 |
|
|
printf("%5s %5s %5s %7s %7s %7s %12s %s\n", "ID", "PID", "VCPUS", |
592 |
|
|
"MAXMEM", "CURMEM", "TTY", "OWNER", "NAME"); |
593 |
|
|
|
594 |
|
|
for (i = 0; i < ct; i++) { |
595 |
|
|
vmi = &list[i]; |
596 |
|
|
vir = &vmi->vir_info; |
597 |
|
|
if (check_info_id(vir->vir_name, vir->vir_id)) { |
598 |
|
|
/* get user name */ |
599 |
|
|
if ((pw = getpwuid(vmi->vir_uid)) == NULL) |
600 |
|
|
(void)snprintf(user, sizeof(user), |
601 |
|
|
"%d", vmi->vir_uid); |
602 |
|
|
else |
603 |
|
|
(void)strlcpy(user, pw->pw_name, |
604 |
|
|
sizeof(user)); |
605 |
|
|
/* get group name */ |
606 |
|
|
if (vmi->vir_gid != -1) { |
607 |
|
|
if (vmi->vir_uid == 0) |
608 |
|
|
*user = '\0'; |
609 |
|
|
if ((gr = getgrgid(vmi->vir_gid)) == NULL) |
610 |
|
|
(void)snprintf(group, sizeof(group), |
611 |
|
|
":%lld", vmi->vir_gid); |
612 |
|
|
else |
613 |
|
|
(void)snprintf(group, sizeof(group), |
614 |
|
|
":%s", gr->gr_name); |
615 |
|
|
(void)strlcat(user, group, sizeof(user)); |
616 |
|
|
} |
617 |
|
|
|
618 |
|
|
(void)strlcpy(curmem, "-", sizeof(curmem)); |
619 |
|
|
(void)strlcpy(maxmem, "-", sizeof(maxmem)); |
620 |
|
|
|
621 |
|
|
(void)fmt_scaled(vir->vir_memory_size * 1024 * 1024, |
622 |
|
|
maxmem); |
623 |
|
|
|
624 |
|
|
if (vir->vir_creator_pid != 0 && vir->vir_id != 0) { |
625 |
|
|
if (*vmi->vir_ttyname == '\0') |
626 |
|
|
tty = "-"; |
627 |
|
|
/* get tty - skip /dev/ path */ |
628 |
|
|
else if ((tty = strrchr(vmi->vir_ttyname, |
629 |
|
|
'/')) == NULL || ++tty == '\0') |
630 |
|
|
tty = list[i].vir_ttyname; |
631 |
|
|
|
632 |
|
|
(void)fmt_scaled(vir->vir_used_size, curmem); |
633 |
|
|
|
634 |
|
|
/* running vm */ |
635 |
|
|
printf("%5u %5u %5zd %7s %7s %7s %12s %s\n", |
636 |
|
|
vir->vir_id, vir->vir_creator_pid, |
637 |
|
|
vir->vir_ncpus, maxmem, curmem, |
638 |
|
|
tty, user, vir->vir_name); |
639 |
|
|
} else { |
640 |
|
|
/* disabled vm */ |
641 |
|
|
printf("%5u %5s %5zd %7s %7s %7s %12s %s\n", |
642 |
|
|
vir->vir_id, "-", |
643 |
|
|
vir->vir_ncpus, maxmem, curmem, |
644 |
|
|
"-", user, vir->vir_name); |
645 |
|
|
} |
646 |
|
|
} |
647 |
|
|
if (check_info_id(vir->vir_name, vir->vir_id) > 0) { |
648 |
|
|
for (j = 0; j < vir->vir_ncpus; j++) { |
649 |
|
|
if (vir->vir_vcpu_state[j] == |
650 |
|
|
VCPU_STATE_STOPPED) |
651 |
|
|
vcpu_state = "STOPPED"; |
652 |
|
|
else if (vir->vir_vcpu_state[j] == |
653 |
|
|
VCPU_STATE_RUNNING) |
654 |
|
|
vcpu_state = "RUNNING"; |
655 |
|
|
else |
656 |
|
|
vcpu_state = "UNKNOWN"; |
657 |
|
|
|
658 |
|
|
printf(" VCPU: %2zd STATE: %s\n", |
659 |
|
|
j, vcpu_state); |
660 |
|
|
} |
661 |
|
|
} |
662 |
|
|
} |
663 |
|
|
} |
664 |
|
|
|
665 |
|
|
/* |
666 |
|
|
* vm_console |
667 |
|
|
* |
668 |
|
|
* Connects to the vm console returned from vmd in 'list'. |
669 |
|
|
* |
670 |
|
|
* Parameters |
671 |
|
|
* list: the vm information (consolidated) returned from vmd via imsg |
672 |
|
|
* ct : the size (number of elements in 'list') of the result |
673 |
|
|
*/ |
674 |
|
|
__dead void |
675 |
|
|
vm_console(struct vmop_info_result *list, size_t ct) |
676 |
|
|
{ |
677 |
|
|
struct vmop_info_result *vir; |
678 |
|
|
size_t i; |
679 |
|
|
|
680 |
|
|
for (i = 0; i < ct; i++) { |
681 |
|
|
vir = &list[i]; |
682 |
|
|
if ((check_info_id(vir->vir_info.vir_name, |
683 |
|
|
vir->vir_info.vir_id) > 0) && |
684 |
|
|
(vir->vir_ttyname[0] != '\0')) { |
685 |
|
|
/* does not return */ |
686 |
|
|
ctl_openconsole(vir->vir_ttyname); |
687 |
|
|
} |
688 |
|
|
} |
689 |
|
|
|
690 |
|
|
errx(1, "console %d not found", info_id); |
691 |
|
|
} |
692 |
|
|
|
693 |
|
|
/* |
694 |
|
|
* create_imagefile |
695 |
|
|
* |
696 |
|
|
* Create an empty imagefile with the specified path and size. |
697 |
|
|
* |
698 |
|
|
* Parameters: |
699 |
|
|
* imgfile_path: path to the image file to create |
700 |
|
|
* imgsize : size of the image file to create (in MB) |
701 |
|
|
* |
702 |
|
|
* Return: |
703 |
|
|
* EEXIST: The requested image file already exists |
704 |
|
|
* 0 : Image file successfully created |
705 |
|
|
* Exxxx : Various other Exxxx errno codes due to other I/O errors |
706 |
|
|
*/ |
707 |
|
|
int |
708 |
|
|
create_imagefile(const char *imgfile_path, long imgsize) |
709 |
|
|
{ |
710 |
|
|
int fd, ret; |
711 |
|
|
|
712 |
|
|
/* Refuse to overwrite an existing image */ |
713 |
|
|
fd = open(imgfile_path, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, |
714 |
|
|
S_IRUSR | S_IWUSR); |
715 |
|
|
if (fd == -1) |
716 |
|
|
return (errno); |
717 |
|
|
|
718 |
|
|
/* Extend to desired size */ |
719 |
|
|
if (ftruncate(fd, (off_t)imgsize * 1024 * 1024) == -1) { |
720 |
|
|
ret = errno; |
721 |
|
|
close(fd); |
722 |
|
|
unlink(imgfile_path); |
723 |
|
|
return (ret); |
724 |
|
|
} |
725 |
|
|
|
726 |
|
|
ret = close(fd); |
727 |
|
|
return (ret); |
728 |
|
|
} |