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 LM_IN_PROGRESS /* opts_next_list() has been called */ 26 }; 27 28 typedef enum ListMode ListMode; 29 30 struct OptsVisitor 31 { 32 Visitor visitor; 33 34 /* Ownership remains with opts_visitor_new()'s caller. */ 35 const QemuOpts *opts_root; 36 37 unsigned depth; 38 39 /* Non-null iff depth is positive. Each key is a QemuOpt name. Each value 40 * is a non-empty GQueue, enumerating all QemuOpt occurrences with that 41 * name. */ 42 GHashTable *unprocessed_opts; 43 44 /* The list currently being traversed with opts_start_list() / 45 * opts_next_list(). The list must have a struct element type in the 46 * schema, with a single mandatory scalar member. */ 47 ListMode list_mode; 48 GQueue *repeated_opts; 49 50 /* If "opts_root->id" is set, reinstantiate it as a fake QemuOpt for 51 * uniformity. Only its "name" and "str" fields are set. "fake_id_opt" does 52 * not survive or escape the OptsVisitor object. 53 */ 54 QemuOpt *fake_id_opt; 55 }; 56 57 58 static void 59 destroy_list(gpointer list) 60 { 61 g_queue_free(list); 62 } 63 64 65 static void 66 opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt) 67 { 68 GQueue *list; 69 70 list = g_hash_table_lookup(unprocessed_opts, opt->name); 71 if (list == NULL) { 72 list = g_queue_new(); 73 74 /* GHashTable will never try to free the keys -- we supply NULL as 75 * "key_destroy_func" in opts_start_struct(). Thus cast away key 76 * const-ness in order to suppress gcc's warning. 77 */ 78 g_hash_table_insert(unprocessed_opts, (gpointer)opt->name, list); 79 } 80 81 /* Similarly, destroy_list() doesn't call g_queue_free_full(). */ 82 g_queue_push_tail(list, (gpointer)opt); 83 } 84 85 86 static void 87 opts_start_struct(Visitor *v, void **obj, const char *kind, 88 const char *name, size_t size, Error **errp) 89 { 90 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 91 const QemuOpt *opt; 92 93 *obj = g_malloc0(size > 0 ? size : 1); 94 if (ov->depth++ > 0) { 95 return; 96 } 97 98 ov->unprocessed_opts = g_hash_table_new_full(&g_str_hash, &g_str_equal, 99 NULL, &destroy_list); 100 QTAILQ_FOREACH(opt, &ov->opts_root->head, next) { 101 /* ensured by qemu-option.c::opts_do_parse() */ 102 assert(strcmp(opt->name, "id") != 0); 103 104 opts_visitor_insert(ov->unprocessed_opts, opt); 105 } 106 107 if (ov->opts_root->id != NULL) { 108 ov->fake_id_opt = g_malloc0(sizeof *ov->fake_id_opt); 109 110 ov->fake_id_opt->name = "id"; 111 ov->fake_id_opt->str = ov->opts_root->id; 112 opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt); 113 } 114 } 115 116 117 static gboolean 118 ghr_true(gpointer ign_key, gpointer ign_value, gpointer ign_user_data) 119 { 120 return TRUE; 121 } 122 123 124 static void 125 opts_end_struct(Visitor *v, Error **errp) 126 { 127 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 128 GQueue *any; 129 130 if (--ov->depth > 0) { 131 return; 132 } 133 134 /* we should have processed all (distinct) QemuOpt instances */ 135 any = g_hash_table_find(ov->unprocessed_opts, &ghr_true, NULL); 136 if (any) { 137 const QemuOpt *first; 138 139 first = g_queue_peek_head(any); 140 error_set(errp, QERR_INVALID_PARAMETER, first->name); 141 } 142 g_hash_table_destroy(ov->unprocessed_opts); 143 ov->unprocessed_opts = NULL; 144 g_free(ov->fake_id_opt); 145 ov->fake_id_opt = NULL; 146 } 147 148 149 static GQueue * 150 lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp) 151 { 152 GQueue *list; 153 154 list = g_hash_table_lookup(ov->unprocessed_opts, name); 155 if (!list) { 156 error_set(errp, QERR_MISSING_PARAMETER, name); 157 } 158 return list; 159 } 160 161 162 static void 163 opts_start_list(Visitor *v, const char *name, Error **errp) 164 { 165 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 166 167 /* we can't traverse a list in a list */ 168 assert(ov->list_mode == LM_NONE); 169 ov->repeated_opts = lookup_distinct(ov, name, errp); 170 if (ov->repeated_opts != NULL) { 171 ov->list_mode = LM_STARTED; 172 } 173 } 174 175 176 static GenericList * 177 opts_next_list(Visitor *v, GenericList **list, Error **errp) 178 { 179 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 180 GenericList **link; 181 182 switch (ov->list_mode) { 183 case LM_STARTED: 184 ov->list_mode = LM_IN_PROGRESS; 185 link = list; 186 break; 187 188 case LM_IN_PROGRESS: { 189 const QemuOpt *opt; 190 191 opt = g_queue_pop_head(ov->repeated_opts); 192 if (g_queue_is_empty(ov->repeated_opts)) { 193 g_hash_table_remove(ov->unprocessed_opts, opt->name); 194 return NULL; 195 } 196 link = &(*list)->next; 197 break; 198 } 199 200 default: 201 abort(); 202 } 203 204 *link = g_malloc0(sizeof **link); 205 return *link; 206 } 207 208 209 static void 210 opts_end_list(Visitor *v, Error **errp) 211 { 212 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 213 214 assert(ov->list_mode == LM_STARTED || ov->list_mode == LM_IN_PROGRESS); 215 ov->repeated_opts = NULL; 216 ov->list_mode = LM_NONE; 217 } 218 219 220 static const QemuOpt * 221 lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp) 222 { 223 if (ov->list_mode == LM_NONE) { 224 GQueue *list; 225 226 /* the last occurrence of any QemuOpt takes effect when queried by name 227 */ 228 list = lookup_distinct(ov, name, errp); 229 return list ? g_queue_peek_tail(list) : NULL; 230 } 231 assert(ov->list_mode == LM_IN_PROGRESS); 232 return g_queue_peek_head(ov->repeated_opts); 233 } 234 235 236 static void 237 processed(OptsVisitor *ov, const char *name) 238 { 239 if (ov->list_mode == LM_NONE) { 240 g_hash_table_remove(ov->unprocessed_opts, name); 241 return; 242 } 243 assert(ov->list_mode == LM_IN_PROGRESS); 244 /* do nothing */ 245 } 246 247 248 static void 249 opts_type_str(Visitor *v, char **obj, const char *name, Error **errp) 250 { 251 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 252 const QemuOpt *opt; 253 254 opt = lookup_scalar(ov, name, errp); 255 if (!opt) { 256 return; 257 } 258 *obj = g_strdup(opt->str ? opt->str : ""); 259 processed(ov, name); 260 } 261 262 263 /* mimics qemu-option.c::parse_option_bool() */ 264 static void 265 opts_type_bool(Visitor *v, bool *obj, const char *name, Error **errp) 266 { 267 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 268 const QemuOpt *opt; 269 270 opt = lookup_scalar(ov, name, errp); 271 if (!opt) { 272 return; 273 } 274 275 if (opt->str) { 276 if (strcmp(opt->str, "on") == 0 || 277 strcmp(opt->str, "yes") == 0 || 278 strcmp(opt->str, "y") == 0) { 279 *obj = true; 280 } else if (strcmp(opt->str, "off") == 0 || 281 strcmp(opt->str, "no") == 0 || 282 strcmp(opt->str, "n") == 0) { 283 *obj = false; 284 } else { 285 error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, 286 "on|yes|y|off|no|n"); 287 return; 288 } 289 } else { 290 *obj = true; 291 } 292 293 processed(ov, name); 294 } 295 296 297 static void 298 opts_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp) 299 { 300 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 301 const QemuOpt *opt; 302 const char *str; 303 long long val; 304 char *endptr; 305 306 opt = lookup_scalar(ov, name, errp); 307 if (!opt) { 308 return; 309 } 310 str = opt->str ? opt->str : ""; 311 312 errno = 0; 313 val = strtoll(str, &endptr, 0); 314 if (*str != '\0' && *endptr == '\0' && errno == 0 && INT64_MIN <= val && 315 val <= INT64_MAX) { 316 *obj = val; 317 processed(ov, name); 318 return; 319 } 320 error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, "an int64 value"); 321 } 322 323 324 static void 325 opts_type_uint64(Visitor *v, uint64_t *obj, const char *name, Error **errp) 326 { 327 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 328 const QemuOpt *opt; 329 const char *str; 330 331 opt = lookup_scalar(ov, name, errp); 332 if (!opt) { 333 return; 334 } 335 336 str = opt->str; 337 if (str != NULL) { 338 while (isspace((unsigned char)*str)) { 339 ++str; 340 } 341 342 if (*str != '-' && *str != '\0') { 343 unsigned long long val; 344 char *endptr; 345 346 /* non-empty, non-negative subject sequence */ 347 errno = 0; 348 val = strtoull(str, &endptr, 0); 349 if (*endptr == '\0' && errno == 0 && val <= UINT64_MAX) { 350 *obj = val; 351 processed(ov, name); 352 return; 353 } 354 } 355 } 356 error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, 357 "an uint64 value"); 358 } 359 360 361 static void 362 opts_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp) 363 { 364 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 365 const QemuOpt *opt; 366 int64_t val; 367 char *endptr; 368 369 opt = lookup_scalar(ov, name, errp); 370 if (!opt) { 371 return; 372 } 373 374 val = strtosz_suffix(opt->str ? opt->str : "", &endptr, 375 STRTOSZ_DEFSUFFIX_B); 376 if (val != -1 && *endptr == '\0') { 377 *obj = val; 378 processed(ov, name); 379 return; 380 } 381 error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, 382 "a size value representible as a non-negative int64"); 383 } 384 385 386 static void 387 opts_start_optional(Visitor *v, bool *present, const char *name, 388 Error **errp) 389 { 390 OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v); 391 392 /* we only support a single mandatory scalar field in a list node */ 393 assert(ov->list_mode == LM_NONE); 394 *present = (lookup_distinct(ov, name, NULL) != NULL); 395 } 396 397 398 OptsVisitor * 399 opts_visitor_new(const QemuOpts *opts) 400 { 401 OptsVisitor *ov; 402 403 ov = g_malloc0(sizeof *ov); 404 405 ov->visitor.start_struct = &opts_start_struct; 406 ov->visitor.end_struct = &opts_end_struct; 407 408 ov->visitor.start_list = &opts_start_list; 409 ov->visitor.next_list = &opts_next_list; 410 ov->visitor.end_list = &opts_end_list; 411 412 /* input_type_enum() covers both "normal" enums and union discriminators. 413 * The union discriminator field is always generated as "type"; it should 414 * match the "type" QemuOpt child of any QemuOpts. 415 * 416 * input_type_enum() will remove the looked-up key from the 417 * "unprocessed_opts" hash even if the lookup fails, because the removal is 418 * done earlier in opts_type_str(). This should be harmless. 419 */ 420 ov->visitor.type_enum = &input_type_enum; 421 422 ov->visitor.type_int = &opts_type_int; 423 ov->visitor.type_uint64 = &opts_type_uint64; 424 ov->visitor.type_size = &opts_type_size; 425 ov->visitor.type_bool = &opts_type_bool; 426 ov->visitor.type_str = &opts_type_str; 427 428 /* type_number() is not filled in, but this is not the first visitor to 429 * skip some mandatory methods... */ 430 431 ov->visitor.start_optional = &opts_start_optional; 432 433 ov->opts_root = opts; 434 435 return ov; 436 } 437 438 439 void 440 opts_visitor_cleanup(OptsVisitor *ov) 441 { 442 if (ov->unprocessed_opts != NULL) { 443 g_hash_table_destroy(ov->unprocessed_opts); 444 } 445 g_free(ov->fake_id_opt); 446 g_free(ov); 447 } 448 449 450 Visitor * 451 opts_get_visitor(OptsVisitor *ov) 452 { 453 return &ov->visitor; 454 } 455