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