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