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