1 /* 2 * Options Visitor 3 * 4 * Copyright Red Hat, Inc. 2012-2016 5 * 6 * Author: Laszlo Ersek <lersek@redhat.com> 7 * 8 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. 9 * See the COPYING.LIB file in the top-level directory. 10 * 11 */ 12 13 #include "qemu/osdep.h" 14 #include "qapi/error.h" 15 #include "qemu/cutils.h" 16 #include "qapi/qmp/qerror.h" 17 #include "qapi/opts-visitor.h" 18 #include "qemu/queue.h" 19 #include "qemu/option_int.h" 20 #include "qapi/visitor-impl.h" 21 22 23 enum ListMode 24 { 25 LM_NONE, /* not traversing a list of repeated options */ 26 27 LM_IN_PROGRESS, /* 28 * opts_next_list() ready to be called. 29 * 30 * Generating the next list link will consume the most 31 * recently parsed QemuOpt instance of the repeated 32 * option. 33 * 34 * Parsing a value into the list link will examine the 35 * next QemuOpt instance of the repeated option, and 36 * possibly enter LM_SIGNED_INTERVAL or 37 * LM_UNSIGNED_INTERVAL. 38 */ 39 40 LM_SIGNED_INTERVAL, /* 41 * opts_next_list() has been called. 42 * 43 * Generating the next list link will consume the most 44 * recently stored element from the signed interval, 45 * parsed from the most recent QemuOpt instance of the 46 * repeated option. This may consume QemuOpt itself 47 * and return to LM_IN_PROGRESS. 48 * 49 * Parsing a value into the list link will store the 50 * next element of the signed interval. 51 */ 52 53 LM_UNSIGNED_INTERVAL, /* Same as above, only for an unsigned interval. */ 54 55 LM_TRAVERSED /* 56 * opts_next_list() has been called. 57 * 58 * No more QemuOpt instance in the list. 59 * The traversal has been completed. 60 */ 61 }; 62 63 typedef enum ListMode ListMode; 64 65 struct OptsVisitor 66 { 67 Visitor visitor; 68 69 /* Ownership remains with opts_visitor_new()'s caller. */ 70 const QemuOpts *opts_root; 71 72 unsigned depth; 73 74 /* Non-null iff depth is positive. Each key is a QemuOpt name. Each value 75 * is a non-empty GQueue, enumerating all QemuOpt occurrences with that 76 * name. */ 77 GHashTable *unprocessed_opts; 78 79 /* The list currently being traversed with opts_start_list() / 80 * opts_next_list(). The list must have a struct element type in the 81 * schema, with a single mandatory scalar member. */ 82 ListMode list_mode; 83 GQueue *repeated_opts; 84 85 /* When parsing a list of repeating options as integers, values of the form 86 * "a-b", representing a closed interval, are allowed. Elements in the 87 * range are generated individually. 88 */ 89 union { 90 int64_t s; 91 uint64_t u; 92 } range_next, range_limit; 93 94 /* If "opts_root->id" is set, reinstantiate it as a fake QemuOpt for 95 * uniformity. Only its "name" and "str" fields are set. "fake_id_opt" does 96 * not survive or escape the OptsVisitor object. 97 */ 98 QemuOpt *fake_id_opt; 99 }; 100 101 102 static OptsVisitor *to_ov(Visitor *v) 103 { 104 return container_of(v, OptsVisitor, visitor); 105 } 106 107 108 static void 109 destroy_list(gpointer list) 110 { 111 g_queue_free(list); 112 } 113 114 115 static void 116 opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt) 117 { 118 GQueue *list; 119 120 list = g_hash_table_lookup(unprocessed_opts, opt->name); 121 if (list == NULL) { 122 list = g_queue_new(); 123 124 /* GHashTable will never try to free the keys -- we supply NULL as 125 * "key_destroy_func" in opts_start_struct(). Thus cast away key 126 * const-ness in order to suppress gcc's warning. 127 */ 128 g_hash_table_insert(unprocessed_opts, (gpointer)opt->name, list); 129 } 130 131 /* Similarly, destroy_list() doesn't call g_queue_free_full(). */ 132 g_queue_push_tail(list, (gpointer)opt); 133 } 134 135 136 static void 137 opts_start_struct(Visitor *v, const char *name, void **obj, 138 size_t size, Error **errp) 139 { 140 OptsVisitor *ov = to_ov(v); 141 const QemuOpt *opt; 142 143 if (obj) { 144 *obj = g_malloc0(size); 145 } 146 if (ov->depth++ > 0) { 147 return; 148 } 149 150 ov->unprocessed_opts = g_hash_table_new_full(&g_str_hash, &g_str_equal, 151 NULL, &destroy_list); 152 QTAILQ_FOREACH(opt, &ov->opts_root->head, next) { 153 /* ensured by qemu-option.c::opts_do_parse() */ 154 assert(strcmp(opt->name, "id") != 0); 155 156 opts_visitor_insert(ov->unprocessed_opts, opt); 157 } 158 159 if (ov->opts_root->id != NULL) { 160 ov->fake_id_opt = g_malloc0(sizeof *ov->fake_id_opt); 161 162 ov->fake_id_opt->name = g_strdup("id"); 163 ov->fake_id_opt->str = g_strdup(ov->opts_root->id); 164 opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt); 165 } 166 } 167 168 169 static void 170 opts_check_struct(Visitor *v, Error **errp) 171 { 172 OptsVisitor *ov = to_ov(v); 173 GHashTableIter iter; 174 GQueue *any; 175 176 if (ov->depth > 1) { 177 return; 178 } 179 180 /* we should have processed all (distinct) QemuOpt instances */ 181 g_hash_table_iter_init(&iter, ov->unprocessed_opts); 182 if (g_hash_table_iter_next(&iter, NULL, (void **)&any)) { 183 const QemuOpt *first; 184 185 first = g_queue_peek_head(any); 186 error_setg(errp, QERR_INVALID_PARAMETER, first->name); 187 } 188 } 189 190 191 static void 192 opts_end_struct(Visitor *v, void **obj) 193 { 194 OptsVisitor *ov = to_ov(v); 195 196 if (--ov->depth > 0) { 197 return; 198 } 199 200 g_hash_table_destroy(ov->unprocessed_opts); 201 ov->unprocessed_opts = NULL; 202 if (ov->fake_id_opt) { 203 g_free(ov->fake_id_opt->name); 204 g_free(ov->fake_id_opt->str); 205 g_free(ov->fake_id_opt); 206 } 207 ov->fake_id_opt = NULL; 208 } 209 210 211 static GQueue * 212 lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp) 213 { 214 GQueue *list; 215 216 list = g_hash_table_lookup(ov->unprocessed_opts, name); 217 if (!list) { 218 error_setg(errp, QERR_MISSING_PARAMETER, name); 219 } 220 return list; 221 } 222 223 224 static void 225 opts_start_list(Visitor *v, const char *name, GenericList **list, size_t size, 226 Error **errp) 227 { 228 OptsVisitor *ov = to_ov(v); 229 230 /* we can't traverse a list in a list */ 231 assert(ov->list_mode == LM_NONE); 232 /* we don't support visits without a list */ 233 assert(list); 234 ov->repeated_opts = lookup_distinct(ov, name, errp); 235 if (ov->repeated_opts) { 236 ov->list_mode = LM_IN_PROGRESS; 237 *list = g_malloc0(size); 238 } else { 239 *list = NULL; 240 } 241 } 242 243 244 static GenericList * 245 opts_next_list(Visitor *v, GenericList *tail, size_t size) 246 { 247 OptsVisitor *ov = to_ov(v); 248 249 switch (ov->list_mode) { 250 case LM_TRAVERSED: 251 return NULL; 252 case LM_SIGNED_INTERVAL: 253 case LM_UNSIGNED_INTERVAL: 254 if (ov->list_mode == LM_SIGNED_INTERVAL) { 255 if (ov->range_next.s < ov->range_limit.s) { 256 ++ov->range_next.s; 257 break; 258 } 259 } else if (ov->range_next.u < ov->range_limit.u) { 260 ++ov->range_next.u; 261 break; 262 } 263 ov->list_mode = LM_IN_PROGRESS; 264 /* range has been completed, fall through in order to pop option */ 265 266 case LM_IN_PROGRESS: { 267 const QemuOpt *opt; 268 269 opt = g_queue_pop_head(ov->repeated_opts); 270 if (g_queue_is_empty(ov->repeated_opts)) { 271 g_hash_table_remove(ov->unprocessed_opts, opt->name); 272 ov->repeated_opts = NULL; 273 ov->list_mode = LM_TRAVERSED; 274 return NULL; 275 } 276 break; 277 } 278 279 default: 280 abort(); 281 } 282 283 tail->next = g_malloc0(size); 284 return tail->next; 285 } 286 287 288 static void 289 opts_check_list(Visitor *v, Error **errp) 290 { 291 /* 292 * Unvisited list elements will be reported later when checking 293 * whether unvisited struct members remain. 294 */ 295 } 296 297 298 static void 299 opts_end_list(Visitor *v, void **obj) 300 { 301 OptsVisitor *ov = to_ov(v); 302 303 assert(ov->list_mode == LM_IN_PROGRESS || 304 ov->list_mode == LM_SIGNED_INTERVAL || 305 ov->list_mode == LM_UNSIGNED_INTERVAL || 306 ov->list_mode == LM_TRAVERSED); 307 ov->repeated_opts = NULL; 308 ov->list_mode = LM_NONE; 309 } 310 311 312 static const QemuOpt * 313 lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp) 314 { 315 if (ov->list_mode == LM_NONE) { 316 GQueue *list; 317 318 /* the last occurrence of any QemuOpt takes effect when queried by name 319 */ 320 list = lookup_distinct(ov, name, errp); 321 return list ? g_queue_peek_tail(list) : NULL; 322 } 323 if (ov->list_mode == LM_TRAVERSED) { 324 error_setg(errp, "Fewer list elements than expected"); 325 return NULL; 326 } 327 assert(ov->list_mode == LM_IN_PROGRESS); 328 return g_queue_peek_head(ov->repeated_opts); 329 } 330 331 332 static void 333 processed(OptsVisitor *ov, const char *name) 334 { 335 if (ov->list_mode == LM_NONE) { 336 g_hash_table_remove(ov->unprocessed_opts, name); 337 return; 338 } 339 assert(ov->list_mode == LM_IN_PROGRESS); 340 /* do nothing */ 341 } 342 343 344 static void 345 opts_type_str(Visitor *v, const char *name, char **obj, Error **errp) 346 { 347 OptsVisitor *ov = to_ov(v); 348 const QemuOpt *opt; 349 350 opt = lookup_scalar(ov, name, errp); 351 if (!opt) { 352 *obj = NULL; 353 return; 354 } 355 *obj = g_strdup(opt->str ? opt->str : ""); 356 /* Note that we consume a string even if this is called as part of 357 * an enum visit that later fails because the string is not a 358 * valid enum value; this is harmless because tracking what gets 359 * consumed only matters to visit_end_struct() as the final error 360 * check if there were no other failures during the visit. */ 361 processed(ov, name); 362 } 363 364 365 /* mimics qemu-option.c::parse_option_bool() */ 366 static void 367 opts_type_bool(Visitor *v, const char *name, bool *obj, Error **errp) 368 { 369 OptsVisitor *ov = to_ov(v); 370 const QemuOpt *opt; 371 372 opt = lookup_scalar(ov, name, errp); 373 if (!opt) { 374 return; 375 } 376 377 if (opt->str) { 378 if (strcmp(opt->str, "on") == 0 || 379 strcmp(opt->str, "yes") == 0 || 380 strcmp(opt->str, "y") == 0) { 381 *obj = true; 382 } else if (strcmp(opt->str, "off") == 0 || 383 strcmp(opt->str, "no") == 0 || 384 strcmp(opt->str, "n") == 0) { 385 *obj = false; 386 } else { 387 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, 388 "on|yes|y|off|no|n"); 389 return; 390 } 391 } else { 392 *obj = true; 393 } 394 395 processed(ov, name); 396 } 397 398 399 static void 400 opts_type_int64(Visitor *v, const char *name, int64_t *obj, Error **errp) 401 { 402 OptsVisitor *ov = to_ov(v); 403 const QemuOpt *opt; 404 const char *str; 405 long long val; 406 char *endptr; 407 408 if (ov->list_mode == LM_SIGNED_INTERVAL) { 409 *obj = ov->range_next.s; 410 return; 411 } 412 413 opt = lookup_scalar(ov, name, errp); 414 if (!opt) { 415 return; 416 } 417 str = opt->str ? opt->str : ""; 418 419 /* we've gotten past lookup_scalar() */ 420 assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS); 421 422 errno = 0; 423 val = strtoll(str, &endptr, 0); 424 if (errno == 0 && endptr > str && INT64_MIN <= val && val <= INT64_MAX) { 425 if (*endptr == '\0') { 426 *obj = val; 427 processed(ov, name); 428 return; 429 } 430 if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) { 431 long long val2; 432 433 str = endptr + 1; 434 val2 = strtoll(str, &endptr, 0); 435 if (errno == 0 && endptr > str && *endptr == '\0' && 436 INT64_MIN <= val2 && val2 <= INT64_MAX && val <= val2 && 437 (val > INT64_MAX - OPTS_VISITOR_RANGE_MAX || 438 val2 < val + OPTS_VISITOR_RANGE_MAX)) { 439 ov->range_next.s = val; 440 ov->range_limit.s = val2; 441 ov->list_mode = LM_SIGNED_INTERVAL; 442 443 /* as if entering on the top */ 444 *obj = ov->range_next.s; 445 return; 446 } 447 } 448 } 449 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, 450 (ov->list_mode == LM_NONE) ? "an int64 value" : 451 "an int64 value or range"); 452 } 453 454 455 static void 456 opts_type_uint64(Visitor *v, const char *name, uint64_t *obj, Error **errp) 457 { 458 OptsVisitor *ov = to_ov(v); 459 const QemuOpt *opt; 460 const char *str; 461 unsigned long long val; 462 char *endptr; 463 464 if (ov->list_mode == LM_UNSIGNED_INTERVAL) { 465 *obj = ov->range_next.u; 466 return; 467 } 468 469 opt = lookup_scalar(ov, name, errp); 470 if (!opt) { 471 return; 472 } 473 str = opt->str; 474 475 /* we've gotten past lookup_scalar() */ 476 assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS); 477 478 if (parse_uint(str, &val, &endptr, 0) == 0 && val <= UINT64_MAX) { 479 if (*endptr == '\0') { 480 *obj = val; 481 processed(ov, name); 482 return; 483 } 484 if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) { 485 unsigned long long val2; 486 487 str = endptr + 1; 488 if (parse_uint_full(str, &val2, 0) == 0 && 489 val2 <= UINT64_MAX && val <= val2 && 490 val2 - val < OPTS_VISITOR_RANGE_MAX) { 491 ov->range_next.u = val; 492 ov->range_limit.u = val2; 493 ov->list_mode = LM_UNSIGNED_INTERVAL; 494 495 /* as if entering on the top */ 496 *obj = ov->range_next.u; 497 return; 498 } 499 } 500 } 501 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, 502 (ov->list_mode == LM_NONE) ? "a uint64 value" : 503 "a uint64 value or range"); 504 } 505 506 507 static void 508 opts_type_size(Visitor *v, const char *name, uint64_t *obj, Error **errp) 509 { 510 OptsVisitor *ov = to_ov(v); 511 const QemuOpt *opt; 512 int err; 513 514 opt = lookup_scalar(ov, name, errp); 515 if (!opt) { 516 return; 517 } 518 519 err = qemu_strtosz(opt->str ? opt->str : "", NULL, obj); 520 if (err < 0) { 521 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, 522 "a size value"); 523 return; 524 } 525 526 processed(ov, name); 527 } 528 529 530 static void 531 opts_optional(Visitor *v, const char *name, bool *present) 532 { 533 OptsVisitor *ov = to_ov(v); 534 535 /* we only support a single mandatory scalar field in a list node */ 536 assert(ov->list_mode == LM_NONE); 537 *present = (lookup_distinct(ov, name, NULL) != NULL); 538 } 539 540 541 static void 542 opts_free(Visitor *v) 543 { 544 OptsVisitor *ov = to_ov(v); 545 546 if (ov->unprocessed_opts != NULL) { 547 g_hash_table_destroy(ov->unprocessed_opts); 548 } 549 g_free(ov->fake_id_opt); 550 g_free(ov); 551 } 552 553 554 Visitor * 555 opts_visitor_new(const QemuOpts *opts) 556 { 557 OptsVisitor *ov; 558 559 assert(opts); 560 ov = g_malloc0(sizeof *ov); 561 562 ov->visitor.type = VISITOR_INPUT; 563 564 ov->visitor.start_struct = &opts_start_struct; 565 ov->visitor.check_struct = &opts_check_struct; 566 ov->visitor.end_struct = &opts_end_struct; 567 568 ov->visitor.start_list = &opts_start_list; 569 ov->visitor.next_list = &opts_next_list; 570 ov->visitor.check_list = &opts_check_list; 571 ov->visitor.end_list = &opts_end_list; 572 573 ov->visitor.type_int64 = &opts_type_int64; 574 ov->visitor.type_uint64 = &opts_type_uint64; 575 ov->visitor.type_size = &opts_type_size; 576 ov->visitor.type_bool = &opts_type_bool; 577 ov->visitor.type_str = &opts_type_str; 578 579 /* type_number() is not filled in, but this is not the first visitor to 580 * skip some mandatory methods... */ 581 582 ov->visitor.optional = &opts_optional; 583 ov->visitor.free = opts_free; 584 585 ov->opts_root = opts; 586 587 return &ov->visitor; 588 } 589