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