]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libfdisk/src/script.c
misc: fix reassigned values before old ones has been used [cppcheck]
[thirdparty/util-linux.git] / libfdisk / src / script.c
1
2 #include "fdiskP.h"
3 #include "strutils.h"
4 #include "carefulputc.h"
5
6 /**
7 * SECTION: script
8 * @title: Script
9 * @short_description: text based sfdisk compatible description of partition table
10 *
11 * The libfdisk scripts are based on original sfdisk script (dumps). Each
12 * script has two parts: script headers and partition table entries
13 * (partitions).
14 *
15 * For more details about script format see sfdisk man page.
16 */
17
18 /* script header (e.g. unit: sectors) */
19 struct fdisk_scriptheader {
20 struct list_head headers;
21 char *name;
22 char *data;
23 };
24
25 /* script control struct */
26 struct fdisk_script {
27 struct fdisk_table *table;
28 struct list_head headers;
29 struct fdisk_context *cxt;
30
31 int refcount;
32 char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *);
33 void *userdata;
34
35 /* parser's state */
36 size_t nlines;
37 struct fdisk_label *label;
38
39 unsigned int json : 1, /* JSON output */
40 force_label : 1; /* label: <name> specified */
41 };
42
43
44 static void fdisk_script_free_header(struct fdisk_scriptheader *fi)
45 {
46 if (!fi)
47 return;
48
49 DBG(SCRIPT, ul_debugobj(fi, "free header %s", fi->name));
50 free(fi->name);
51 free(fi->data);
52 list_del(&fi->headers);
53 free(fi);
54 }
55
56 /**
57 * fdisk_new_script:
58 * @cxt: context
59 *
60 * The script hold fdisk_table and additional information to read/write
61 * script to the file.
62 *
63 * Returns: newly allocated script struct.
64 */
65 struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt)
66 {
67 struct fdisk_script *dp = NULL;
68
69 dp = calloc(1, sizeof(*dp));
70 if (!dp)
71 return NULL;
72
73 DBG(SCRIPT, ul_debugobj(dp, "alloc"));
74 dp->refcount = 1;
75 dp->cxt = cxt;
76 fdisk_ref_context(cxt);
77
78 dp->table = fdisk_new_table();
79 if (!dp->table) {
80 fdisk_unref_script(dp);
81 return NULL;
82 }
83
84 INIT_LIST_HEAD(&dp->headers);
85 return dp;
86 }
87
88 /**
89 * fdisk_new_script_from_file:
90 * @cxt: context
91 * @filename: path to the script file
92 *
93 * Allocates a new script and reads script from @filename.
94 *
95 * Returns: new script instance or NULL in case of error (check errno for more details).
96 */
97 struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
98 const char *filename)
99 {
100 int rc;
101 FILE *f;
102 struct fdisk_script *dp, *res = NULL;
103
104 assert(cxt);
105 assert(filename);
106
107 DBG(SCRIPT, ul_debug("opening %s", filename));
108 f = fopen(filename, "r");
109 if (!f)
110 return NULL;
111
112 dp = fdisk_new_script(cxt);
113 if (!dp)
114 goto done;
115
116 rc = fdisk_script_read_file(dp, f);
117 if (rc) {
118 errno = -rc;
119 goto done;
120 }
121
122 res = dp;
123 done:
124 fclose(f);
125 if (!res)
126 fdisk_unref_script(dp);
127 else
128 errno = 0;
129
130 return res;
131 }
132
133 /**
134 * fdisk_ref_script:
135 * @dp: script pointer
136 *
137 * Increments reference counter.
138 */
139 void fdisk_ref_script(struct fdisk_script *dp)
140 {
141 if (dp)
142 dp->refcount++;
143 }
144
145 static void fdisk_reset_script(struct fdisk_script *dp)
146 {
147 assert(dp);
148
149 DBG(SCRIPT, ul_debugobj(dp, "reset"));
150 fdisk_unref_table(dp->table);
151 dp->table = NULL;
152
153 while (!list_empty(&dp->headers)) {
154 struct fdisk_scriptheader *fi = list_entry(dp->headers.next,
155 struct fdisk_scriptheader, headers);
156 fdisk_script_free_header(fi);
157 }
158 INIT_LIST_HEAD(&dp->headers);
159 }
160
161 /**
162 * fdisk_unref_script:
163 * @dp: script pointer
164 *
165 * Decrements reference counter, on zero the @dp is automatically
166 * deallocated.
167 */
168 void fdisk_unref_script(struct fdisk_script *dp)
169 {
170 if (!dp)
171 return;
172
173 dp->refcount--;
174 if (dp->refcount <= 0) {
175 fdisk_reset_script(dp);
176 fdisk_unref_context(dp->cxt);
177 DBG(SCRIPT, ul_debugobj(dp, "free script"));
178 free(dp);
179 }
180 }
181
182 /**
183 * fdisk_script_set_userdata
184 * @dp: script
185 * @data: your data
186 *
187 * Sets data usable for example in callbacks (e.g fdisk_script_set_fgets()).
188 *
189 * Returns: 0 on success, <0 on error.
190 */
191 int fdisk_script_set_userdata(struct fdisk_script *dp, void *data)
192 {
193 assert(dp);
194 dp->userdata = data;
195 return 0;
196 }
197
198 /**
199 * fdisk_script_get_userdata
200 * @dp: script
201 *
202 * Returns: user data or NULL.
203 */
204 void *fdisk_script_get_userdata(struct fdisk_script *dp)
205 {
206 assert(dp);
207 return dp->userdata;
208 }
209
210 static struct fdisk_scriptheader *script_get_header(struct fdisk_script *dp,
211 const char *name)
212 {
213 struct list_head *p;
214
215 list_for_each(p, &dp->headers) {
216 struct fdisk_scriptheader *fi = list_entry(p, struct fdisk_scriptheader, headers);
217
218 if (strcasecmp(fi->name, name) == 0)
219 return fi;
220 }
221
222 return NULL;
223 }
224
225 /**
226 * fdisk_script_get_header:
227 * @dp: script instance
228 * @name: header name
229 *
230 * Returns: pointer to header data or NULL.
231 */
232 const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name)
233 {
234 struct fdisk_scriptheader *fi;
235
236 assert(dp);
237 assert(name);
238
239 fi = script_get_header(dp, name);
240 return fi ? fi->data : NULL;
241 }
242
243
244 /**
245 * fdisk_script_set_header:
246 * @dp: script instance
247 * @name: header name
248 * @data: header data (or NULL)
249 *
250 * The headers are used as global options for whole partition
251 * table, always one header per line.
252 *
253 * If no @data is specified then the header is removed. If header does not exist
254 * and @data is specified then a new header is added.
255 *
256 * Note that libfdisk allows to specify arbitrary custom header, the default
257 * build-in headers are "unit" and "label", and some label specific headers
258 * (for example "uuid" and "name" for GPT).
259 *
260 * Returns: 0 on success, <0 on error
261 */
262 int fdisk_script_set_header(struct fdisk_script *dp,
263 const char *name,
264 const char *data)
265 {
266 struct fdisk_scriptheader *fi;
267
268 if (!dp || !name)
269 return -EINVAL;
270
271 fi = script_get_header(dp, name);
272 if (!fi && !data)
273 return 0; /* want to remove header that does not exist, success */
274
275 if (!data) {
276 DBG(SCRIPT, ul_debugobj(dp, "freeing header %s", name));
277
278 /* no data, remove the header */
279 fdisk_script_free_header(fi);
280 return 0;
281 }
282
283 if (!fi) {
284 DBG(SCRIPT, ul_debugobj(dp, "setting new header %s='%s'", name, data));
285
286 /* new header */
287 fi = calloc(1, sizeof(*fi));
288 if (!fi)
289 return -ENOMEM;
290 INIT_LIST_HEAD(&fi->headers);
291 fi->name = strdup(name);
292 fi->data = strdup(data);
293 if (!fi->data || !fi->name) {
294 fdisk_script_free_header(fi);
295 return -ENOMEM;
296 }
297 list_add_tail(&fi->headers, &dp->headers);
298 } else {
299 /* update existing */
300 char *x = strdup(data);
301
302 DBG(SCRIPT, ul_debugobj(dp, "update '%s' header '%s' -> '%s'", name, fi->data, data));
303
304 if (!x)
305 return -ENOMEM;
306 free(fi->data);
307 fi->data = x;
308 }
309
310 if (strcmp(name, "label") == 0)
311 dp->label = NULL;
312
313 return 0;
314 }
315
316 /**
317 * fdisk_script_get_table:
318 * @dp: script
319 *
320 * The table (container with partitions) is possible to create by
321 * fdisk_script_read_context() or fdisk_script_read_file(), otherwise
322 * this function returns NULL.
323 *
324 * Returns: NULL or script.
325 */
326 struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp)
327 {
328 assert(dp);
329 return dp ? dp->table : NULL;
330 }
331
332 static struct fdisk_label *script_get_label(struct fdisk_script *dp)
333 {
334 assert(dp);
335 assert(dp->cxt);
336
337 if (!dp->label) {
338 dp->label = fdisk_get_label(dp->cxt,
339 fdisk_script_get_header(dp, "label"));
340 DBG(SCRIPT, ul_debugobj(dp, "label '%s'", dp->label ? dp->label->name : ""));
341 }
342 return dp->label;
343 }
344
345 /**
346 * fdisk_script_get_nlines:
347 * @dp: script
348 *
349 * Returns: number of parsed lines or <0 on error.
350 */
351 int fdisk_script_get_nlines(struct fdisk_script *dp)
352 {
353 assert(dp);
354 return dp->nlines;
355 }
356
357 /**
358 * fdisk_script_has_force_label:
359 * @dp: script
360 *
361 * Label has been explicitly specified in the script.
362 *
363 * Since: 2.30
364 *
365 * Returns: true if "label: name" has been parsed.
366 */
367 int fdisk_script_has_force_label(struct fdisk_script *dp)
368 {
369 assert(dp);
370 return dp->force_label;
371 }
372
373
374 /**
375 * fdisk_script_read_context:
376 * @dp: script
377 * @cxt: context
378 *
379 * Reads data from the @cxt context (on disk partition table) into the script.
380 * If the context is no specified than defaults to context used for fdisk_new_script().
381 *
382 * Return: 0 on success, <0 on error.
383 */
384 int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt)
385 {
386 struct fdisk_label *lb;
387 int rc;
388 char *p = NULL;
389
390 if (!dp || (!cxt && !dp->cxt))
391 return -EINVAL;
392
393 if (!cxt)
394 cxt = dp->cxt;
395
396 DBG(SCRIPT, ul_debugobj(dp, "reading context into script"));
397 fdisk_reset_script(dp);
398
399 lb = fdisk_get_label(cxt, NULL);
400 if (!lb)
401 return -EINVAL;
402
403 /* allocate and fill new table */
404 rc = fdisk_get_partitions(cxt, &dp->table);
405 if (rc)
406 return rc;
407
408 /* generate headers */
409 rc = fdisk_script_set_header(dp, "label", fdisk_label_get_name(lb));
410
411 if (!rc && fdisk_get_disklabel_id(cxt, &p) == 0 && p) {
412 rc = fdisk_script_set_header(dp, "label-id", p);
413 free(p);
414 }
415 if (!rc && cxt->dev_path)
416 rc = fdisk_script_set_header(dp, "device", cxt->dev_path);
417 if (!rc)
418 rc = fdisk_script_set_header(dp, "unit", "sectors");
419
420 if (!rc && fdisk_is_label(cxt, GPT)) {
421 struct fdisk_labelitem item;
422 char buf[64];
423
424 /* first-lba */
425 rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_FIRSTLBA, &item);
426 if (!rc) {
427 snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
428 rc = fdisk_script_set_header(dp, "first-lba", buf);
429 }
430
431 /* last-lba */
432 if (!rc)
433 rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_LASTLBA, &item);
434 if (!rc) {
435 snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
436 rc = fdisk_script_set_header(dp, "last-lba", buf);
437 }
438
439 /* table-length */
440 if (!rc) {
441 size_t n = fdisk_get_npartitions(cxt);
442 if (n != FDISK_GPT_NPARTITIONS_DEFAULT) {
443 snprintf(buf, sizeof(buf), "%zu", n);
444 rc = fdisk_script_set_header(dp, "table-length", buf);
445 }
446 }
447 }
448
449 DBG(SCRIPT, ul_debugobj(dp, "read context done [rc=%d]", rc));
450 return rc;
451 }
452
453 /**
454 * fdisk_script_enable_json:
455 * @dp: script
456 * @json: 0 or 1
457 *
458 * Disable/Enable JSON output format.
459 *
460 * Returns: 0 on success, <0 on error.
461 */
462 int fdisk_script_enable_json(struct fdisk_script *dp, int json)
463 {
464 assert(dp);
465
466 dp->json = json;
467 return 0;
468 }
469
470 static void fput_indent(int indent, FILE *f)
471 {
472 int i;
473
474 for (i = 0; i <= indent; i++)
475 fputs(" ", f);
476 }
477
478 static int write_file_json(struct fdisk_script *dp, FILE *f)
479 {
480 struct list_head *h;
481 struct fdisk_partition *pa;
482 struct fdisk_iter itr;
483 const char *devname = NULL;
484 int ct = 0, indent = 0;
485
486 assert(dp);
487 assert(f);
488
489 DBG(SCRIPT, ul_debugobj(dp, "writing json dump to file"));
490
491 fputs("{\n", f);
492
493 fput_indent(indent, f);
494 fputs("\"partitiontable\": {\n", f);
495 indent++;
496
497 /* script headers */
498 list_for_each(h, &dp->headers) {
499 struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
500 const char *name = fi->name;
501 int num = 0;
502
503 if (strcmp(name, "first-lba") == 0) {
504 name = "firstlba";
505 num = 1;
506 } else if (strcmp(name, "last-lba") == 0) {
507 name = "lastlba";
508 num = 1;
509 } else if (strcmp(name, "label-id") == 0)
510 name = "id";
511
512 fput_indent(indent, f);
513 fputs_quoted_lower(name, f);
514 fputs(": ", f);
515 if (!num)
516 fputs_quoted(fi->data, f);
517 else
518 fputs(fi->data, f);
519 if (!dp->table && fi == list_last_entry(&dp->headers, struct fdisk_scriptheader, headers))
520 fputc('\n', f);
521 else
522 fputs(",\n", f);
523
524 if (strcmp(name, "device") == 0)
525 devname = fi->data;
526 }
527
528
529 if (!dp->table) {
530 DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
531 goto done;
532 }
533
534 DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
535
536 fput_indent(indent, f);
537 fputs("\"partitions\": [\n", f);
538 indent++;
539
540 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
541 while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
542 char *p = NULL;
543
544 ct++;
545 fput_indent(indent, f);
546 fputc('{', f);
547 if (devname)
548 p = fdisk_partname(devname, pa->partno + 1);
549 if (p) {
550 DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
551 fputs("\"node\": ", f);
552 fputs_quoted(p, f);
553 }
554
555 if (fdisk_partition_has_start(pa))
556 fprintf(f, ", \"start\": %ju", (uintmax_t)pa->start);
557 if (fdisk_partition_has_size(pa))
558 fprintf(f, ", \"size\": %ju", (uintmax_t)pa->size);
559
560 if (pa->type && fdisk_parttype_get_string(pa->type))
561 fprintf(f, ", \"type\": \"%s\"", fdisk_parttype_get_string(pa->type));
562 else if (pa->type)
563 fprintf(f, ", \"type\": \"%x\"", fdisk_parttype_get_code(pa->type));
564
565 if (pa->uuid)
566 fprintf(f, ", \"uuid\": \"%s\"", pa->uuid);
567 if (pa->name && *pa->name) {
568 fputs(", \"name\": ", f),
569 fputs_quoted(pa->name, f);
570 }
571
572 /* for MBR attr=80 means bootable */
573 if (pa->attrs) {
574 struct fdisk_label *lb = script_get_label(dp);
575
576 if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
577 fprintf(f, ", \"attrs\": \"%s\"", pa->attrs);
578 }
579 if (fdisk_partition_is_bootable(pa))
580 fprintf(f, ", \"bootable\": true");
581
582 if ((size_t)ct < fdisk_table_get_nents(dp->table))
583 fputs("},\n", f);
584 else
585 fputs("}\n", f);
586 }
587
588 indent--;
589 fput_indent(indent, f);
590 fputs("]\n", f);
591 done:
592 indent--;
593 fput_indent(indent, f);
594 fputs("}\n}\n", f);
595
596 DBG(SCRIPT, ul_debugobj(dp, "write script done"));
597 return 0;
598 }
599
600 static int write_file_sfdisk(struct fdisk_script *dp, FILE *f)
601 {
602 struct list_head *h;
603 struct fdisk_partition *pa;
604 struct fdisk_iter itr;
605 const char *devname = NULL;
606
607 assert(dp);
608 assert(f);
609
610 DBG(SCRIPT, ul_debugobj(dp, "writing sfdisk-like script to file"));
611
612 /* script headers */
613 list_for_each(h, &dp->headers) {
614 struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
615 fprintf(f, "%s: %s\n", fi->name, fi->data);
616 if (strcmp(fi->name, "device") == 0)
617 devname = fi->data;
618 }
619
620 if (!dp->table) {
621 DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
622 return 0;
623 }
624
625 DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
626
627 fputc('\n', f);
628
629 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
630 while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
631 char *p = NULL;
632
633 if (devname)
634 p = fdisk_partname(devname, pa->partno + 1);
635 if (p) {
636 DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
637 fprintf(f, "%s :", p);
638 } else
639 fprintf(f, "%zu :", pa->partno + 1);
640
641 if (fdisk_partition_has_start(pa))
642 fprintf(f, " start=%12ju", (uintmax_t)pa->start);
643 if (fdisk_partition_has_size(pa))
644 fprintf(f, ", size=%12ju", (uintmax_t)pa->size);
645
646 if (pa->type && fdisk_parttype_get_string(pa->type))
647 fprintf(f, ", type=%s", fdisk_parttype_get_string(pa->type));
648 else if (pa->type)
649 fprintf(f, ", type=%x", fdisk_parttype_get_code(pa->type));
650
651 if (pa->uuid)
652 fprintf(f, ", uuid=%s", pa->uuid);
653 if (pa->name && *pa->name)
654 fprintf(f, ", name=\"%s\"", pa->name);
655
656 /* for MBR attr=80 means bootable */
657 if (pa->attrs) {
658 struct fdisk_label *lb = script_get_label(dp);
659
660 if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
661 fprintf(f, ", attrs=\"%s\"", pa->attrs);
662 }
663 if (fdisk_partition_is_bootable(pa))
664 fprintf(f, ", bootable");
665 fputc('\n', f);
666 }
667
668 DBG(SCRIPT, ul_debugobj(dp, "write script done"));
669 return 0;
670 }
671
672 /**
673 * fdisk_script_write_file:
674 * @dp: script
675 * @f: output file
676 *
677 * Writes script @dp to the file @f.
678 *
679 * Returns: 0 on success, <0 on error.
680 */
681 int fdisk_script_write_file(struct fdisk_script *dp, FILE *f)
682 {
683 assert(dp);
684
685 if (dp->json)
686 return write_file_json(dp, f);
687
688 return write_file_sfdisk(dp, f);
689 }
690
691 static inline int is_header_line(const char *s)
692 {
693 const char *p = strchr(s, ':');
694
695 if (!p || p == s || !*(p + 1) || strchr(s, '='))
696 return 0;
697
698 return 1;
699 }
700
701 /* parses "<name>: value", note modifies @s*/
702 static int parse_line_header(struct fdisk_script *dp, char *s)
703 {
704 int rc = -EINVAL;
705 char *name, *value;
706
707 DBG(SCRIPT, ul_debugobj(dp, " parse header '%s'", s));
708
709 if (!s || !*s)
710 return -EINVAL;
711
712 name = s;
713 value = strchr(s, ':');
714 if (!value)
715 goto done;
716 *value = '\0';
717 value++;
718
719 ltrim_whitespace((unsigned char *) name);
720 rtrim_whitespace((unsigned char *) name);
721 ltrim_whitespace((unsigned char *) value);
722 rtrim_whitespace((unsigned char *) value);
723
724 if (strcmp(name, "label") == 0) {
725 if (dp->cxt && !fdisk_get_label(dp->cxt, value))
726 goto done; /* unknown label name */
727 dp->force_label = 1;
728 } else if (strcmp(name, "unit") == 0) {
729 if (strcmp(value, "sectors") != 0)
730 goto done; /* only "sectors" supported */
731 } else if (strcmp(name, "label-id") == 0
732 || strcmp(name, "device") == 0
733 || strcmp(name, "first-lba") == 0
734 || strcmp(name, "last-lba") == 0
735 || strcmp(name, "table-length") == 0) {
736 ; /* whatever is possible */
737 } else
738 goto done; /* unknown header */
739
740 if (*name && *value)
741 rc = fdisk_script_set_header(dp, name, value);
742 done:
743 if (rc)
744 DBG(SCRIPT, ul_debugobj(dp, "header parse error: "
745 "[rc=%d, name='%s', value='%s']",
746 rc, name, value));
747 return rc;
748
749 }
750
751 /* returns zero terminated string with next token and @str is updated */
752 static char *next_token(char **str)
753 {
754 char *tk_begin = NULL,
755 *tk_end = NULL,
756 *end = NULL,
757 *p;
758 int open_quote = 0, terminated = 0;
759
760 for (p = *str; p && *p; p++) {
761 if (!tk_begin) {
762 if (isblank(*p))
763 continue;
764 tk_begin = *p == '"' ? p + 1 : p;
765 }
766 if (*p == '"')
767 open_quote ^= 1;
768 if (open_quote)
769 continue;
770 if (isblank(*p) || *p == ',' || *p == ';' || *p == '"' )
771 tk_end = p;
772 else if (*(p + 1) == '\0')
773 tk_end = p + 1;
774 if (tk_begin && tk_end)
775 break;
776 }
777
778 if (!tk_end)
779 return NULL;
780
781 end = tk_end;
782
783 /* skip closing quotes */
784 if (*end == '"')
785 end++;
786
787 /* token is terminated by blank (or blank is before "," or ";") */
788 if (isblank(*end)) {
789 end = (char *) skip_blank(end);
790 terminated++;
791 }
792
793 /* token is terminated by "," or ";" */
794 if (*end == ',' || *end == ';') {
795 end++;
796 terminated++;
797
798 /* token is terminated by \0 */
799 } else if (!*end)
800 terminated++;
801
802 if (!terminated) {
803 DBG(SCRIPT, ul_debug("unterminated token '%s'", end));
804 return NULL;
805 }
806
807 /* skip extra space after terminator */
808 end = (char *) skip_blank(end);
809
810 *tk_end = '\0';
811 *str = end;
812 return tk_begin;
813 }
814
815 static int next_number(char **s, uint64_t *num, int *power)
816 {
817 char *tk;
818 int rc = -EINVAL;
819
820 assert(num);
821 assert(s);
822
823 tk = next_token(s);
824 if (tk)
825 rc = parse_size(tk, (uintmax_t *) num, power);
826 return rc;
827 }
828
829 static int next_string(char **s, char **str)
830 {
831 char *tk;
832 int rc = -EINVAL;
833
834 assert(s);
835 assert(str);
836
837 tk = next_token(s);
838 if (tk) {
839 *str = strdup(tk);
840 rc = !*str ? -ENOMEM : 0;
841 }
842 return rc;
843 }
844
845 static int partno_from_devname(char *s)
846 {
847 int pno;
848 size_t sz;
849 char *end, *p;
850
851 sz = rtrim_whitespace((unsigned char *)s);
852 p = s + sz - 1;
853
854 while (p > s && isdigit(*(p - 1)))
855 p--;
856
857 errno = 0;
858 pno = strtol(p, &end, 10);
859 if (errno || !end || p == end)
860 return -1;
861 return pno - 1;
862 }
863
864 /* dump format
865 * <device>: start=<num>, size=<num>, type=<string>, ...
866 */
867 static int parse_line_nameval(struct fdisk_script *dp, char *s)
868 {
869 char *p, *x;
870 struct fdisk_partition *pa;
871 int rc = 0;
872 uint64_t num;
873 int pno;
874
875 assert(dp);
876 assert(s);
877
878 DBG(SCRIPT, ul_debugobj(dp, " parse script line: '%s'", s));
879
880 pa = fdisk_new_partition();
881 if (!pa)
882 return -ENOMEM;
883
884 fdisk_partition_start_follow_default(pa, 1);
885 fdisk_partition_end_follow_default(pa, 1);
886 fdisk_partition_partno_follow_default(pa, 1);
887
888 /* set partno */
889 p = strchr(s, ':');
890 x = strchr(s, '=');
891 if (p && (!x || p < x)) {
892 *p = '\0';
893 p++;
894
895 pno = partno_from_devname(s);
896 if (pno >= 0) {
897 fdisk_partition_partno_follow_default(pa, 0);
898 fdisk_partition_set_partno(pa, pno);
899 }
900 } else
901 p = s;
902
903 while (rc == 0 && p && *p) {
904
905 DBG(SCRIPT, ul_debugobj(dp, " parsing '%s'", p));
906 p = (char *) skip_blank(p);
907
908 if (!strncasecmp(p, "start=", 6)) {
909 int pow = 0;
910 p += 6;
911 rc = next_number(&p, &num, &pow);
912 if (!rc) {
913 if (pow) /* specified as <num><suffix> */
914 num /= dp->cxt->sector_size;
915 fdisk_partition_set_start(pa, num);
916 fdisk_partition_start_follow_default(pa, 0);
917 }
918 } else if (!strncasecmp(p, "size=", 5)) {
919 int pow = 0;
920
921 p += 5;
922 rc = next_number(&p, &num, &pow);
923 if (!rc) {
924 if (pow) /* specified as <num><suffix> */
925 num /= dp->cxt->sector_size;
926 else /* specified as number of sectors */
927 fdisk_partition_size_explicit(pa, 1);
928 fdisk_partition_set_size(pa, num);
929 fdisk_partition_end_follow_default(pa, 0);
930 }
931
932 } else if (!strncasecmp(p, "bootable", 8)) {
933 /* we use next_token() to skip possible extra space */
934 char *tk = next_token(&p);
935 if (tk && strcasecmp(tk, "bootable") == 0)
936 pa->boot = 1;
937 else
938 rc = -EINVAL;
939
940 } else if (!strncasecmp(p, "attrs=", 6)) {
941 p += 6;
942 rc = next_string(&p, &pa->attrs);
943
944 } else if (!strncasecmp(p, "uuid=", 5)) {
945 p += 5;
946 rc = next_string(&p, &pa->uuid);
947
948 } else if (!strncasecmp(p, "name=", 5)) {
949 p += 5;
950 rc = next_string(&p, &pa->name);
951
952 } else if (!strncasecmp(p, "type=", 5) ||
953 !strncasecmp(p, "Id=", 3)) { /* backward compatibility */
954 char *type;
955
956 p += ((*p == 'I' || *p == 'i') ? 3 : 5); /* "Id=", "type=" */
957
958 rc = next_string(&p, &type);
959 if (rc)
960 break;
961 pa->type = fdisk_label_parse_parttype(
962 script_get_label(dp), type);
963 free(type);
964
965 if (!pa->type) {
966 rc = -EINVAL;
967 fdisk_unref_parttype(pa->type);
968 pa->type = NULL;
969 break;
970 }
971
972 } else {
973 DBG(SCRIPT, ul_debugobj(dp, "script parse error: unknown field '%s'", p));
974 rc = -EINVAL;
975 break;
976 }
977 }
978
979 if (!rc)
980 rc = fdisk_table_add_partition(dp->table, pa);
981 if (rc)
982 DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
983
984 fdisk_unref_partition(pa);
985 return rc;
986 }
987
988 /* original sfdisk supports partition types shortcuts like 'L' = Linux native
989 */
990 static struct fdisk_parttype *translate_type_shortcuts(struct fdisk_script *dp, char *str)
991 {
992 struct fdisk_label *lb;
993 const char *type = NULL;
994
995 if (strlen(str) != 1)
996 return NULL;
997
998 lb = script_get_label(dp);
999 if (!lb)
1000 return NULL;
1001
1002 if (lb->id == FDISK_DISKLABEL_DOS) {
1003 switch (*str) {
1004 case 'L': /* Linux */
1005 type = "83";
1006 break;
1007 case 'S': /* Swap */
1008 type = "82";
1009 break;
1010 case 'E': /* Dos extended */
1011 type = "05";
1012 break;
1013 case 'X': /* Linux extended */
1014 type = "85";
1015 break;
1016 case 'U': /* UEFI system */
1017 type = "EF";
1018 break;
1019 }
1020 } else if (lb->id == FDISK_DISKLABEL_GPT) {
1021 switch (*str) {
1022 case 'L': /* Linux */
1023 type = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
1024 break;
1025 case 'S': /* Swap */
1026 type = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F";
1027 break;
1028 case 'H': /* Home */
1029 type = "933AC7E1-2EB4-4F13-B844-0E14E2AEF915";
1030 break;
1031 case 'U': /* UEFI system */
1032 type = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B";
1033 break;
1034 }
1035 }
1036
1037 return type ? fdisk_label_parse_parttype(lb, type) : NULL;
1038 }
1039
1040 #define TK_PLUS 1
1041 #define TK_MINUS -1
1042
1043 #define alone_sign(_sign, _p) (_sign && (*_p == '\0' || isblank(*_p)))
1044
1045 /* simple format:
1046 * <start>, <size>, <type>, <bootable>, ...
1047 */
1048 static int parse_line_valcommas(struct fdisk_script *dp, char *s)
1049 {
1050 int rc = 0;
1051 char *p = s, *str;
1052 struct fdisk_partition *pa;
1053 enum { ITEM_START, ITEM_SIZE, ITEM_TYPE, ITEM_BOOTABLE };
1054 int item = -1;
1055
1056 assert(dp);
1057 assert(s);
1058
1059 pa = fdisk_new_partition();
1060 if (!pa)
1061 return -ENOMEM;
1062
1063 fdisk_partition_start_follow_default(pa, 1);
1064 fdisk_partition_end_follow_default(pa, 1);
1065 fdisk_partition_partno_follow_default(pa, 1);
1066
1067 while (rc == 0 && p && *p) {
1068 uint64_t num;
1069 char *begin;
1070 int sign = 0;
1071
1072 p = (char *) skip_blank(p);
1073 item++;
1074
1075 if (item != ITEM_BOOTABLE) {
1076 sign = *p == '-' ? TK_MINUS : *p == '+' ? TK_PLUS : 0;
1077 if (sign)
1078 p++;
1079 }
1080
1081 DBG(SCRIPT, ul_debugobj(dp, " parsing item %d ('%s')", item, p));
1082 begin = p;
1083
1084 switch (item) {
1085 case ITEM_START:
1086 if (*p == ',' || *p == ';' || alone_sign(sign, p))
1087 fdisk_partition_start_follow_default(pa, 1);
1088 else {
1089 int pow = 0;
1090
1091 rc = next_number(&p, &num, &pow);
1092 if (!rc) {
1093 if (pow) /* specified as <num><suffix> */
1094 num /= dp->cxt->sector_size;
1095 fdisk_partition_set_start(pa, num);
1096 pa->movestart = sign == TK_MINUS ? FDISK_MOVE_DOWN :
1097 sign == TK_PLUS ? FDISK_MOVE_UP :
1098 FDISK_MOVE_NONE;
1099 }
1100 fdisk_partition_start_follow_default(pa, 0);
1101 }
1102 break;
1103 case ITEM_SIZE:
1104 if (*p == ',' || *p == ';' || alone_sign(sign, p)) {
1105 fdisk_partition_end_follow_default(pa, 1);
1106 if (sign == TK_PLUS)
1107 /* '+' alone means use all possible space, '-' alone means nothing */
1108 pa->resize = FDISK_RESIZE_ENLARGE;
1109 } else {
1110 int pow = 0;
1111 rc = next_number(&p, &num, &pow);
1112 if (!rc) {
1113 if (pow) /* specified as <size><suffix> */
1114 num /= dp->cxt->sector_size;
1115 else /* specified as number of sectors */
1116 fdisk_partition_size_explicit(pa, 1);
1117 fdisk_partition_set_size(pa, num);
1118 pa->resize = sign == TK_MINUS ? FDISK_RESIZE_REDUCE :
1119 sign == TK_PLUS ? FDISK_RESIZE_ENLARGE :
1120 FDISK_RESIZE_NONE;
1121 }
1122 fdisk_partition_end_follow_default(pa, 0);
1123 }
1124 break;
1125 case ITEM_TYPE:
1126 if (*p == ',' || *p == ';' || alone_sign(sign, p))
1127 break; /* use default type */
1128
1129 rc = next_string(&p, &str);
1130 if (rc)
1131 break;
1132
1133 pa->type = translate_type_shortcuts(dp, str);
1134 if (!pa->type)
1135 pa->type = fdisk_label_parse_parttype(
1136 script_get_label(dp), str);
1137 free(str);
1138
1139 if (!pa->type) {
1140 rc = -EINVAL;
1141 fdisk_unref_parttype(pa->type);
1142 pa->type = NULL;
1143 break;
1144 }
1145 break;
1146 case ITEM_BOOTABLE:
1147 if (*p == ',' || *p == ';')
1148 break;
1149 else {
1150 char *tk = next_token(&p);
1151 if (tk && *tk == '*' && *(tk + 1) == '\0')
1152 pa->boot = 1;
1153 else if (tk && *tk == '-' && *(tk + 1) == '\0')
1154 pa->boot = 0;
1155 else if (tk && *tk == '+' && *(tk + 1) == '\0')
1156 pa->boot = 1;
1157 else
1158 rc = -EINVAL;
1159 }
1160 break;
1161 default:
1162 break;
1163 }
1164
1165 if (begin == p)
1166 p++;
1167 }
1168
1169 if (!rc)
1170 rc = fdisk_table_add_partition(dp->table, pa);
1171 if (rc)
1172 DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
1173
1174 fdisk_unref_partition(pa);
1175 return rc;
1176 }
1177
1178 /* modifies @s ! */
1179 static int fdisk_script_read_buffer(struct fdisk_script *dp, char *s)
1180 {
1181 int rc = 0;
1182
1183 assert(dp);
1184 assert(s);
1185
1186 DBG(SCRIPT, ul_debugobj(dp, " parsing buffer"));
1187
1188 s = (char *) skip_blank(s);
1189 if (!s || !*s)
1190 return 0; /* nothing baby, ignore */
1191
1192 if (!dp->table) {
1193 dp->table = fdisk_new_table();
1194 if (!dp->table)
1195 return -ENOMEM;
1196 }
1197
1198 /* parse header lines only if no partition specified yet */
1199 if (fdisk_table_is_empty(dp->table) && is_header_line(s))
1200 rc = parse_line_header(dp, s);
1201
1202 /* parse script format */
1203 else if (strchr(s, '='))
1204 rc = parse_line_nameval(dp, s);
1205
1206 /* parse simple <value>, ... format */
1207 else
1208 rc = parse_line_valcommas(dp, s);
1209
1210 if (rc)
1211 DBG(SCRIPT, ul_debugobj(dp, "%zu: parse error [rc=%d]",
1212 dp->nlines, rc));
1213 return rc;
1214 }
1215
1216 /**
1217 * fdisk_script_set_fgets:
1218 * @dp: script
1219 * @fn_fgets: callback function
1220 *
1221 * The library uses fgets() function to read the next line from the script.
1222 * This default maybe overridden by another function. Note that the function has
1223 * to return the line terminated by \n (for example readline(3) removes \n).
1224 *
1225 * Return: 0 on success, <0 on error
1226 */
1227 int fdisk_script_set_fgets(struct fdisk_script *dp,
1228 char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *))
1229 {
1230 assert(dp);
1231
1232 dp->fn_fgets = fn_fgets;
1233 return 0;
1234 }
1235
1236 /**
1237 * fdisk_script_read_line:
1238 * @dp: script
1239 * @f: file
1240 * @buf: buffer to store one line of the file
1241 * @bufsz: buffer size
1242 *
1243 * Reads next line into dump.
1244 *
1245 * Returns: 0 on success, <0 on error, 1 when nothing to read.
1246 */
1247 int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz)
1248 {
1249 char *s;
1250
1251 assert(dp);
1252 assert(f);
1253
1254 DBG(SCRIPT, ul_debugobj(dp, " parsing line %zu", dp->nlines));
1255
1256 /* read the next non-blank non-comment line */
1257 do {
1258 if (dp->fn_fgets) {
1259 if (dp->fn_fgets(dp, buf, bufsz, f) == NULL)
1260 return 1;
1261 } else if (fgets(buf, bufsz, f) == NULL)
1262 return 1;
1263
1264 dp->nlines++;
1265 s = strchr(buf, '\n');
1266 if (!s) {
1267 /* Missing final newline? Otherwise an extremely */
1268 /* long line - assume file was corrupted */
1269 if (feof(f)) {
1270 DBG(SCRIPT, ul_debugobj(dp, "no final newline"));
1271 s = strchr(buf, '\0');
1272 } else {
1273 DBG(SCRIPT, ul_debugobj(dp,
1274 "%zu: missing newline at line", dp->nlines));
1275 return -EINVAL;
1276 }
1277 }
1278
1279 *s = '\0';
1280 if (--s >= buf && *s == '\r')
1281 *s = '\0';
1282 s = (char *) skip_blank(buf);
1283 } while (*s == '\0' || *s == '#');
1284
1285 return fdisk_script_read_buffer(dp, s);
1286 }
1287
1288
1289 /**
1290 * fdisk_script_read_file:
1291 * @dp: script
1292 * @f: input file
1293 *
1294 * Reads file @f into script @dp.
1295 *
1296 * Returns: 0 on success, <0 on error.
1297 */
1298 int fdisk_script_read_file(struct fdisk_script *dp, FILE *f)
1299 {
1300 char buf[BUFSIZ];
1301 int rc = 1;
1302
1303 assert(dp);
1304 assert(f);
1305
1306 DBG(SCRIPT, ul_debugobj(dp, "parsing file"));
1307
1308 while (!feof(f)) {
1309 rc = fdisk_script_read_line(dp, f, buf, sizeof(buf));
1310 if (rc)
1311 break;
1312 }
1313
1314 if (rc == 1)
1315 rc = 0; /* end of file */
1316
1317 DBG(SCRIPT, ul_debugobj(dp, "parsing file done [rc=%d]", rc));
1318 return rc;
1319 }
1320
1321 /**
1322 * fdisk_set_script:
1323 * @cxt: context
1324 * @dp: script (or NULL to remove previous reference)
1325 *
1326 * Sets reference to the @dp script and remove reference to the previously used
1327 * script.
1328 *
1329 * The script headers might be used by label drivers to overwrite
1330 * built-in defaults (for example disk label Id) and label driver might
1331 * optimize the default semantic to be more usable for scripts (for example to
1332 * not ask for primary/logical/extended partition type).
1333 *
1334 * Note that script also contains reference to the fdisk context (see
1335 * fdisk_new_script()). This context may be completely independent on
1336 * context used for fdisk_set_script().
1337 *
1338 * Returns: <0 on error, 0 on success.
1339 */
1340 int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp)
1341 {
1342 assert(cxt);
1343
1344 /* unref old */
1345 if (cxt->script)
1346 fdisk_unref_script(cxt->script);
1347
1348 /* ref new */
1349 cxt->script = dp;
1350 if (cxt->script) {
1351 DBG(CXT, ul_debugobj(cxt, "setting reference to script %p", cxt->script));
1352 fdisk_ref_script(cxt->script);
1353 }
1354
1355 return 0;
1356 }
1357
1358 /**
1359 * fdisk_get_script:
1360 * @cxt: context
1361 *
1362 * Returns: the current script or NULL.
1363 */
1364 struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt)
1365 {
1366 assert(cxt);
1367 return cxt->script;
1368 }
1369
1370 /**
1371 * fdisk_apply_script_headers:
1372 * @cxt: context
1373 * @dp: script
1374 *
1375 * Associate context @cxt with script @dp and creates a new empty disklabel.
1376 *
1377 * Returns: 0 on success, <0 on error.
1378 */
1379 int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp)
1380 {
1381 const char *name;
1382 const char *str;
1383 int rc;
1384
1385 assert(cxt);
1386 assert(dp);
1387
1388 DBG(SCRIPT, ul_debugobj(dp, "applying script headers"));
1389 fdisk_set_script(cxt, dp);
1390
1391 /* create empty label */
1392 name = fdisk_script_get_header(dp, "label");
1393 if (!name)
1394 return -EINVAL;
1395
1396 rc = fdisk_create_disklabel(cxt, name);
1397 if (rc)
1398 return rc;
1399
1400 str = fdisk_script_get_header(dp, "table-length");
1401 if (str) {
1402 uintmax_t sz;
1403
1404 rc = parse_size(str, &sz, NULL);
1405 if (rc == 0)
1406 rc = fdisk_gpt_set_npartitions(cxt, sz);
1407 }
1408
1409 return rc;
1410 }
1411
1412 /**
1413 * fdisk_apply_script:
1414 * @cxt: context
1415 * @dp: script
1416 *
1417 * This function creates a new disklabel and partition within context @cxt. You
1418 * have to call fdisk_write_disklabel() to apply changes to the device.
1419 *
1420 * Returns: 0 on error, <0 on error.
1421 */
1422 int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp)
1423 {
1424 int rc;
1425 struct fdisk_script *old;
1426
1427 assert(dp);
1428 assert(cxt);
1429
1430 DBG(CXT, ul_debugobj(cxt, "applying script %p", dp));
1431
1432 old = fdisk_get_script(cxt);
1433 fdisk_ref_script(old);
1434
1435 /* create empty disk label */
1436 rc = fdisk_apply_script_headers(cxt, dp);
1437
1438 /* create partitions */
1439 if (!rc && dp->table)
1440 rc = fdisk_apply_table(cxt, dp->table);
1441
1442 fdisk_set_script(cxt, old);
1443 fdisk_unref_script(old);
1444
1445 DBG(CXT, ul_debugobj(cxt, "script done [rc=%d]", rc));
1446 return rc;
1447 }
1448
1449 #ifdef TEST_PROGRAM
1450 static int test_dump(struct fdisk_test *ts, int argc, char *argv[])
1451 {
1452 char *devname = argv[1];
1453 struct fdisk_context *cxt;
1454 struct fdisk_script *dp;
1455
1456 cxt = fdisk_new_context();
1457 fdisk_assign_device(cxt, devname, 1);
1458
1459 dp = fdisk_new_script(cxt);
1460 fdisk_script_read_context(dp, NULL);
1461
1462 fdisk_script_write_file(dp, stdout);
1463 fdisk_unref_script(dp);
1464 fdisk_unref_context(cxt);
1465
1466 return 0;
1467 }
1468
1469 static int test_read(struct fdisk_test *ts, int argc, char *argv[])
1470 {
1471 char *filename = argv[1];
1472 struct fdisk_script *dp;
1473 struct fdisk_context *cxt;
1474 FILE *f;
1475
1476 if (!(f = fopen(filename, "r")))
1477 err(EXIT_FAILURE, "%s: cannot open", filename);
1478
1479 cxt = fdisk_new_context();
1480 dp = fdisk_new_script(cxt);
1481
1482 fdisk_script_read_file(dp, f);
1483 fclose(f);
1484
1485 fdisk_script_write_file(dp, stdout);
1486 fdisk_unref_script(dp);
1487 fdisk_unref_context(cxt);
1488
1489 return 0;
1490 }
1491
1492 static int test_stdin(struct fdisk_test *ts, int argc, char *argv[])
1493 {
1494 char buf[BUFSIZ];
1495 struct fdisk_script *dp;
1496 struct fdisk_context *cxt;
1497 int rc = 0;
1498
1499 cxt = fdisk_new_context();
1500 dp = fdisk_new_script(cxt);
1501 fdisk_script_set_header(dp, "label", "dos");
1502
1503 printf("<start>, <size>, <type>, <bootable: *|->\n");
1504 do {
1505 struct fdisk_partition *pa;
1506 size_t n = fdisk_table_get_nents(dp->table);
1507
1508 printf(" #%zu :\n", n + 1);
1509 rc = fdisk_script_read_line(dp, stdin, buf, sizeof(buf));
1510
1511 if (rc == 0) {
1512 pa = fdisk_table_get_partition(dp->table, n);
1513 printf(" #%zu %12ju %12ju\n", n + 1,
1514 (uintmax_t)fdisk_partition_get_start(pa),
1515 (uintmax_t)fdisk_partition_get_size(pa));
1516 }
1517 } while (rc == 0);
1518
1519 if (!rc)
1520 fdisk_script_write_file(dp, stdout);
1521 fdisk_unref_script(dp);
1522 fdisk_unref_context(cxt);
1523
1524 return rc;
1525 }
1526
1527 static int test_apply(struct fdisk_test *ts, int argc, char *argv[])
1528 {
1529 char *devname = argv[1], *scriptname = argv[2];
1530 struct fdisk_context *cxt;
1531 struct fdisk_script *dp;
1532 struct fdisk_table *tb = NULL;
1533 struct fdisk_iter *itr = NULL;
1534 struct fdisk_partition *pa = NULL;
1535 int rc;
1536
1537 cxt = fdisk_new_context();
1538 fdisk_assign_device(cxt, devname, 0);
1539
1540 dp = fdisk_new_script_from_file(cxt, scriptname);
1541 if (!dp)
1542 return -errno;
1543
1544 rc = fdisk_apply_script(cxt, dp);
1545 if (rc)
1546 goto done;
1547 fdisk_unref_script(dp);
1548
1549 /* list result */
1550 fdisk_list_disklabel(cxt);
1551 fdisk_get_partitions(cxt, &tb);
1552
1553 itr = fdisk_new_iter(FDISK_ITER_FORWARD);
1554 while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
1555 printf(" #%zu %12ju %12ju\n", fdisk_partition_get_partno(pa),
1556 (uintmax_t)fdisk_partition_get_start(pa),
1557 (uintmax_t)fdisk_partition_get_size(pa));
1558 }
1559
1560 done:
1561 fdisk_free_iter(itr);
1562 fdisk_unref_table(tb);
1563
1564 /*fdisk_write_disklabel(cxt);*/
1565 fdisk_unref_context(cxt);
1566 return 0;
1567 }
1568
1569 static int test_tokens(struct fdisk_test *ts, int argc, char *argv[])
1570 {
1571 char *p, *str = argc == 2 ? strdup(argv[1]) : NULL;
1572 int i;
1573
1574 for (i = 1, p = str; p && *p; i++) {
1575 char *tk = next_token(&p);
1576
1577 if (!tk)
1578 break;
1579
1580 printf("#%d: '%s'\n", i, tk);
1581 }
1582
1583 free(str);
1584 return 0;
1585 }
1586
1587 int main(int argc, char *argv[])
1588 {
1589 struct fdisk_test tss[] = {
1590 { "--dump", test_dump, "<device> dump PT as script" },
1591 { "--read", test_read, "<file> read PT script from file" },
1592 { "--apply", test_apply, "<device> <file> try apply script from file to device" },
1593 { "--stdin", test_stdin, " read input like sfdisk" },
1594 { "--tokens", test_tokens, "<string> parse string" },
1595 { NULL }
1596 };
1597
1598 return fdisk_run_test(tss, argc, argv);
1599 }
1600
1601 #endif