Change parser interface to allow stat
[petitboot] / test / parser / utils.c
1
2 #include <assert.h>
3 #include <err.h>
4 #include <fcntl.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <ctype.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10
11 #include <talloc/talloc.h>
12 #include <types/types.h>
13 #include <url/url.h>
14
15 #include "device-handler.h"
16 #include "parser.h"
17 #include "resource.h"
18 #include "event.h"
19 #include "platform.h"
20
21 #include "parser-test.h"
22
23 struct p_item {
24         struct list_item list;
25         struct parser *parser;
26 };
27
28 struct test_file {
29         struct discover_device  *dev;
30         enum {
31                 TEST_FILE,
32                 TEST_DIR,
33         }                       type;
34         const char              *name;
35         void                    *data;
36         int                     size;
37         struct list_item        list;
38 };
39
40 STATIC_LIST(parsers);
41
42 void __register_parser(struct parser *parser)
43 {
44         struct p_item* i = talloc(NULL, struct p_item);
45
46         i->parser = parser;
47         list_add(&parsers, &i->list);
48 }
49
50 static void __attribute__((destructor)) __cleanup_parsers(void)
51 {
52         struct p_item *item, *tmp;
53
54         list_for_each_entry_safe(&parsers, item, tmp, list)
55                 talloc_free(item);
56 }
57
58 static struct discover_device *test_create_device_simple(
59                 struct parser_test *test)
60 {
61         static int dev_idx;
62         char name[10];
63
64         sprintf(name, "__test%d", dev_idx++);
65
66         return test_create_device(test, name);
67 }
68
69 struct discover_device *test_create_device(struct parser_test *test,
70                 const char *name)
71 {
72         struct discover_device *dev;
73
74         dev = discover_device_create(test->handler, name);
75
76         dev->device->id = talloc_strdup(dev, name);
77         dev->device_path = talloc_asprintf(dev, "/dev/%s", name);
78         dev->mount_path = talloc_asprintf(dev, "/test/mount/%s", name);
79         dev->mounted = true;
80
81         return dev;
82 }
83
84 static struct discover_context *test_create_context(struct parser_test *test)
85 {
86         struct discover_context *ctx;
87
88         ctx = talloc_zero(test, struct discover_context);
89         assert(ctx);
90
91         list_init(&ctx->boot_options);
92         ctx->device = test_create_device_simple(test);
93         ctx->test_data = test;
94         device_handler_add_device(test->handler, ctx->device);
95
96         return ctx;
97 }
98
99 /* define our own test platform */
100 static bool test_platform_probe(struct platform *p __attribute__((unused)),
101                 void *ctx __attribute__((unused)))
102 {
103         return true;
104 }
105
106 struct platform test_platform = {
107         .name = "test",
108         .probe = test_platform_probe,
109 };
110
111 register_platform(test_platform);
112
113 struct parser_test *test_init(void)
114 {
115         struct parser_test *test;
116
117         test = talloc_zero(NULL, struct parser_test);
118         platform_init(NULL);
119         test->handler = device_handler_init(NULL, NULL, 0);
120         test->ctx = test_create_context(test);
121         list_init(&test->files);
122
123         return test;
124 }
125
126 void test_fini(struct parser_test *test)
127 {
128         device_handler_destroy(test->handler);
129         talloc_free(test);
130         platform_fini();
131 }
132
133 void __test_read_conf_data(struct parser_test *test,
134                 struct discover_device *dev, const char *conf_file,
135                 const char *buf, size_t len)
136 {
137         test_add_file_data(test, dev, conf_file, buf, len);
138 }
139
140 void test_read_conf_file(struct parser_test *test, const char *filename,
141                 const char *conf_file)
142 {
143         struct stat stat;
144         size_t size;
145         char *path;
146         int fd, rc;
147         char *buf;
148
149         path = talloc_asprintf(test, "%s/%s", TEST_CONF_BASE, filename);
150
151         fd = open(path, O_RDONLY);
152         if (fd < 0)
153                 err(EXIT_FAILURE, "Can't open test conf file %s\n", path);
154
155         rc = fstat(fd, &stat);
156         assert(!rc);
157         (void)rc;
158
159         size = stat.st_size;
160         buf = talloc_array(test, char, size + 1);
161
162         rc = read(fd, buf, size);
163         assert(rc == (ssize_t)size);
164
165         *(buf + size) = '\0';
166
167         close(fd);
168         talloc_free(path);
169
170         test_add_file_data(test, test->ctx->device, conf_file, buf, size);
171 }
172
173 void test_add_file_data(struct parser_test *test, struct discover_device *dev,
174                 const char *filename, const void *data, int size)
175 {
176         struct test_file *file;
177
178         file = talloc_zero(test, struct test_file);
179         file->type = TEST_FILE;
180         file->dev = dev;
181         file->name = filename;
182         file->data = talloc_memdup(test, data, size);
183         file->size = size;
184         list_add(&test->files, &file->list);
185 }
186
187 void test_add_dir(struct parser_test *test, struct discover_device *dev,
188                 const char *dirname)
189 {
190         struct test_file *file;
191
192         file = talloc_zero(test, struct test_file);
193         file->type = TEST_DIR;
194         file->dev = dev;
195         file->name = dirname;
196         /* Pick a non-zero size for directories so that "[ -s <dir
197          * path> ]" sees that the file has non-zero size. */
198         file->size = 1;
199         list_add(&test->files, &file->list);
200 }
201
202 void test_set_event_source(struct parser_test *test)
203 {
204         test->ctx->event = talloc_zero(test->ctx, struct event);
205 }
206
207 void test_set_event_param(struct event *event, const char *name,
208                 const char *value)
209 {
210         event_set_param(event, name, value);
211 }
212
213 void test_set_event_device(struct event *event, const char *dev)
214 {
215         event->device = talloc_strdup(event, dev);
216 }
217
218 int parser_request_file(struct discover_context *ctx,
219                 struct discover_device *dev, const char *filename,
220                 char **buf, int *len)
221 {
222         struct parser_test *test = ctx->test_data;
223         struct test_file *file;
224         char *tmp;
225
226         list_for_each_entry(&test->files, file, list) {
227                 if (file->dev != dev)
228                         continue;
229                 if (strcmp(file->name, filename))
230                         continue;
231                 if (file->type != TEST_FILE)
232                         continue;
233
234                 /* the read_file() interface always adds a trailing null
235                  * for string-safety; do the same here */
236                 tmp = talloc_array(test, char, file->size + 1);
237                 memcpy(tmp, file->data, file->size);
238                 tmp[file->size] = '\0';
239                 *buf = tmp;
240                 *len = file->size;
241                 return 0;
242         }
243
244         return -1;
245 }
246
247 int parser_stat_path(struct discover_context *ctx,
248                 struct discover_device *dev, const char *path,
249                 struct stat *statbuf)
250 {
251         struct parser_test *test = ctx->test_data;
252         struct test_file *file;
253
254         list_for_each_entry(&test->files, file, list) {
255                 if (file->dev != dev)
256                         continue;
257                 if (strcmp(file->name, path))
258                         continue;
259
260                 statbuf->st_size = (off_t)file->size;
261                 switch (file->type) {
262                 case TEST_FILE:
263                         statbuf->st_mode = S_IFREG;
264                         break;
265                 case TEST_DIR:
266                         statbuf->st_mode = S_IFDIR;
267                         break;
268                 default:
269                         fprintf(stderr, "%s: bad test file mode %d!", __func__,
270                                 file->type);
271                         exit(EXIT_FAILURE);
272                 }
273
274                 return 0;
275         }
276
277         return -1;
278 }
279
280 int parser_replace_file(struct discover_context *ctx,
281                 struct discover_device *dev, const char *filename,
282                 char *buf, int len)
283 {
284         struct parser_test *test = ctx->test_data;
285         struct test_file *f, *file;
286
287         list_for_each_entry(&test->files, f, list) {
288                 if (f->dev != dev)
289                         continue;
290                 if (strcmp(f->name, filename))
291                         continue;
292
293                 file = f;
294                 break;
295         }
296
297         if (!file) {
298                 file = talloc_zero(test, struct test_file);
299                 file->dev = dev;
300                 file->name = filename;
301                 list_add(&test->files, &file->list);
302         }
303
304         file->data = talloc_memdup(test, buf, len);
305         file->size = len;
306         return 0;
307 }
308
309 int parser_request_url(struct discover_context *ctx, struct pb_url *url,
310                 char **buf, int *len)
311 {
312         struct parser_test *test = ctx->test_data;
313         struct test_file *file;
314         char *tmp;
315
316         list_for_each_entry(&test->files, file, list) {
317                 if (file->dev)
318                         continue;
319
320                 if (strcmp(file->name, url->full))
321                         continue;
322
323                 /* the read_file() interface always adds a trailing null
324                  * for string-safety; do the same here */
325                 tmp = talloc_array(test, char, file->size + 1);
326                 memcpy(tmp, file->data, file->size);
327                 tmp[file->size] = '\0';
328                 *buf = tmp;
329                 *len = file->size;
330                 return 0;
331         }
332
333         return -1;
334 }
335
336 int test_run_parser(struct parser_test *test, const char *parser_name)
337 {
338         struct p_item* i;
339
340         list_for_each_entry(&parsers, i, list) {
341                 if (strcmp(i->parser->name, parser_name))
342                         continue;
343                 test->ctx->parser = i->parser;
344                 return i->parser->parse(test->ctx);
345         }
346
347         errx(EXIT_FAILURE, "%s: parser '%s' not found", __func__, parser_name);
348 }
349
350 bool resource_resolve(struct device_handler *handler, struct parser *parser,
351                 struct resource *resource)
352 {
353         if (!resource)
354                 return true;
355         if (resource->resolved)
356                 return true;
357
358         assert(parser);
359         assert(parser->resolve_resource);
360
361         return parser->resolve_resource(handler, resource);
362 }
363
364 void boot_option_resolve(struct device_handler *handler,
365                 struct discover_boot_option *opt)
366 {
367         resource_resolve(handler, opt->source, opt->boot_image);
368         resource_resolve(handler, opt->source, opt->initrd);
369         resource_resolve(handler, opt->source, opt->icon);
370 }
371
372 void test_hotplug_device(struct parser_test *test, struct discover_device *dev)
373 {
374         struct discover_boot_option *opt;
375
376         device_handler_add_device(test->handler, dev);
377
378         list_for_each_entry(&test->ctx->boot_options, opt, list)
379                 boot_option_resolve(test->handler, opt);
380 }
381
382 void test_remove_device(struct parser_test *test, struct discover_device *dev)
383 {
384         struct discover_boot_option *opt, *tmp;
385
386         if (dev == test->ctx->device) {
387                 list_for_each_entry_safe(&test->ctx->boot_options,
388                                 opt, tmp, list) {
389                         list_remove(&opt->list);
390                         talloc_free(opt);
391                 }
392         }
393
394         device_handler_remove(test->handler, dev);
395 }
396
397 struct discover_boot_option *get_boot_option(struct discover_context *ctx,
398                 int idx)
399 {
400         struct discover_boot_option *opt;
401         int i = 0;
402
403         list_for_each_entry(&ctx->boot_options, opt, list) {
404                 if (i++ == idx)
405                         return opt;
406         }
407
408         assert(0);
409
410         return NULL;
411 }
412
413 void __check_boot_option_count(struct discover_context *ctx, int count,
414                 const char *file, int line)
415 {
416         struct discover_boot_option *opt;
417         int defaults = 0, i = 0;
418
419         list_for_each_entry(&ctx->boot_options, opt, list) {
420                 i++;
421                 if (opt->option->is_default)
422                         defaults++;
423         }
424
425         if (defaults > 1) {
426                 fprintf(stderr, "%s:%d: parser returned multiple default "
427                                 "options\n", file, line);
428                 exit(EXIT_FAILURE);
429         }
430
431         if (i == count)
432                 return;
433
434         fprintf(stderr, "%s:%d: boot option count check failed\n", file, line);
435         fprintf(stderr, "expected %d options, got %d:\n", count, i);
436
437         i = 1;
438         list_for_each_entry(&ctx->boot_options, opt, list)
439                 fprintf(stderr, "  %2d: %s [%s]\n", i++, opt->option->name,
440                                 opt->option->id);
441
442         exit(EXIT_FAILURE);
443 }
444
445 void __check_args(struct discover_boot_option *opt, const char *args,
446                 const char *file, int line)
447 {
448         int rc;
449
450         if (!opt->option->boot_args && !args)
451                 return;
452
453         if (!opt->option->boot_args) {
454                 fprintf(stderr, "%s:%d: arg check failed\n", file, line);
455                 fprintf(stderr, "  no arguments parsed\n");
456                 fprintf(stderr, "  expected '%s'\n", args);
457                 exit(EXIT_FAILURE);
458         }
459
460         rc = strcmp(opt->option->boot_args, args);
461         if (rc) {
462                 fprintf(stderr, "%s:%d: arg check failed\n", file, line);
463                 fprintf(stderr, "  got      '%s'\n", opt->option->boot_args);
464                 fprintf(stderr, "  expected '%s'\n", args);
465                 exit(EXIT_FAILURE);
466         }
467 }
468
469 void __check_name(struct discover_boot_option *opt, const char *name,
470                 const char *file, int line)
471 {
472         int rc;
473
474         rc = strcmp(opt->option->name, name);
475         if (rc) {
476                 fprintf(stderr, "%s:%d: name check failed\n", file, line);
477                 fprintf(stderr, "  got      '%s'\n", opt->option->name);
478                 fprintf(stderr, "  expected '%s'\n", name);
479                 exit(EXIT_FAILURE);
480         }
481 }
482
483 void __check_is_default(struct discover_boot_option *opt,
484                 const char *file, int line)
485 {
486         if (opt->option->is_default)
487                 return;
488
489         fprintf(stderr, "%s:%d: default check failed\n", file, line);
490         exit(EXIT_FAILURE);
491 }
492
493 void __check_resolved_local_resource(struct resource *res,
494                 struct discover_device *dev, const char *local_path,
495                 const char *file, int line)
496 {
497         const char *exp_url, *got_url;
498
499         if (!res)
500                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
501
502         if (!res->resolved)
503                 errx(EXIT_FAILURE, "%s:%d: Resource is not resolved",
504                                 file, line);
505
506         exp_url = talloc_asprintf(res, "file://%s%s",
507                         dev->mount_path, local_path);
508         got_url = pb_url_to_string(res->url);
509
510         if (strcmp(got_url, exp_url)) {
511                 fprintf(stderr, "%s:%d: Resource mismatch\n", file, line);
512                 fprintf(stderr, "  got      '%s'\n", got_url);
513                 fprintf(stderr, "  expected '%s'\n", exp_url);
514                 exit(EXIT_FAILURE);
515         }
516 }
517
518 void __check_resolved_url_resource(struct resource *res,
519                 const char *url, const char *file, int line)
520 {
521         char *res_url;
522
523         if (!res)
524                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
525
526         if (!res->resolved)
527                 errx(EXIT_FAILURE, "%s:%d: Resource is not resolved",
528                                 file, line);
529
530         res_url = pb_url_to_string(res->url);
531         if (strcmp(url, res_url)) {
532                 fprintf(stderr, "%s:%d: Resource mismatch\n", file, line);
533                 fprintf(stderr, "  got      '%s'\n", res_url);
534                 fprintf(stderr, "  expected '%s'\n", url);
535                 exit(EXIT_FAILURE);
536         }
537 }
538 void __check_unresolved_resource(struct resource *res,
539                 const char *file, int line)
540 {
541         if (!res)
542                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
543
544         if (res->resolved)
545                 errx(EXIT_FAILURE, "%s:%d: Resource is resolved", file, line);
546 }
547
548 void __check_not_present_resource(struct resource *res,
549                 const char *file, int line)
550 {
551         if (res)
552                 errx(EXIT_FAILURE, "%s:%d: Resource present", file, line);
553 }
554
555 static void dump_file_data(const void *buf, int len)
556 {
557         int i, j, hex_len = strlen("00 ");
558         const int row_len = 16;
559
560         for (i = 0; i < len; i += row_len) {
561                 char hbuf[row_len * hex_len + 1];
562                 char cbuf[row_len + strlen("|") + 1];
563
564                 for (j = 0; (j < row_len) && ((i+j) < len); j++) {
565                         char c = ((const char *)buf)[i + j];
566
567                         snprintf(hbuf + j * hex_len, hex_len + 1, "%02x ", c);
568
569                         if (!isprint(c))
570                                 c = '.';
571
572                         snprintf(cbuf + j, hex_len + 1, "%c", c);
573                 }
574
575                 strcat(cbuf, "|");
576
577                 fprintf(stderr, "%08x  %*s |%s\n", i,
578                                 0 - (int)sizeof(hbuf) + 1, hbuf, cbuf);
579         }
580 }
581
582 void __check_file_contents(struct parser_test *test,
583                 struct discover_device *dev, const char *filename,
584                 const char *buf, int len,
585                 const char *srcfile, int srcline)
586 {
587         struct test_file *f, *file = NULL;
588
589         list_for_each_entry(&test->files, f, list) {
590                 if (f->dev != dev)
591                         continue;
592                 if (strcmp(f->name, filename))
593                         continue;
594
595                 file = f;
596                 break;
597         }
598
599         if (!file)
600                 errx(EXIT_FAILURE, "%s:%d: File '%s' not found",
601                                 srcfile, srcline, filename);
602
603         if (file->size != len || memcmp(file->data, buf, len)) {
604                 fprintf(stderr, "%s:%d: File '%s' data/size mismatch\n",
605                                 srcfile, srcline, filename);
606                 fprintf(stderr, "Expected:\n");
607                 dump_file_data(buf, len);
608                 fprintf(stderr, "Got:\n");
609                 dump_file_data(file->data, file->size);
610                 exit(EXIT_FAILURE);
611         }
612 }