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