]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libfdisk/src/script.c
956d4f52e638421cefdac8a734dc51fc4e9be5fb
[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_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(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(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(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 /* first-lba */
407 rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_FIRSTLBA, &item);
408 if (!rc) {
409 snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
410 rc = fdisk_script_set_header(dp, "first-lba", buf);
411 }
412
413 /* last-lba */
414 if (!rc)
415 rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_LASTLBA, &item);
416 if (!rc) {
417 snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
418 rc = fdisk_script_set_header(dp, "last-lba", buf);
419 }
420
421 /* table-length */
422 if (!rc) {
423 size_t n = fdisk_get_npartitions(cxt);
424 if (n != FDISK_GPT_NPARTITIONS_DEFAULT) {
425 snprintf(buf, sizeof(buf), "%zu", n);
426 rc = fdisk_script_set_header(dp, "table-length", buf);
427 }
428 }
429 }
430
431 DBG(SCRIPT, ul_debugobj(dp, "read context done [rc=%d]", rc));
432 return rc;
433 }
434
435 /**
436 * fdisk_script_enable_json:
437 * @dp: script
438 * @json: 0 or 1
439 *
440 * Disable/Enable JSON output format.
441 *
442 * Returns: 0 on success, <0 on error.
443 */
444 int fdisk_script_enable_json(struct fdisk_script *dp, int json)
445 {
446 assert(dp);
447
448 dp->json = json;
449 return 0;
450 }
451
452 static void fput_indent(int indent, FILE *f)
453 {
454 int i;
455
456 for (i = 0; i <= indent; i++)
457 fputs(" ", f);
458 }
459
460 static int write_file_json(struct fdisk_script *dp, FILE *f)
461 {
462 struct list_head *h;
463 struct fdisk_partition *pa;
464 struct fdisk_iter itr;
465 const char *devname = NULL;
466 int ct = 0, indent = 0;
467
468 assert(dp);
469 assert(f);
470
471 DBG(SCRIPT, ul_debugobj(dp, "writing json dump to file"));
472
473 fputs("{\n", f);
474
475 fput_indent(indent, f);
476 fputs("\"partitiontable\": {\n", f);
477 indent++;
478
479 /* script headers */
480 list_for_each(h, &dp->headers) {
481 struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
482 const char *name = fi->name;
483 int num = 0;
484
485 if (strcmp(name, "first-lba") == 0) {
486 name = "firstlba";
487 num = 1;
488 } else if (strcmp(name, "last-lba") == 0) {
489 name = "lastlba";
490 num = 1;
491 } else if (strcmp(name, "label-id") == 0)
492 name = "id";
493
494 fput_indent(indent, f);
495 fputs_quoted_lower(name, f);
496 fputs(": ", f);
497 if (!num)
498 fputs_quoted(fi->data, f);
499 else
500 fputs(fi->data, f);
501 if (!dp->table && fi == list_last_entry(&dp->headers, struct fdisk_scriptheader, headers))
502 fputc('\n', f);
503 else
504 fputs(",\n", f);
505
506 if (strcmp(name, "device") == 0)
507 devname = fi->data;
508 }
509
510
511 if (!dp->table) {
512 DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
513 goto done;
514 }
515
516 DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
517
518 fput_indent(indent, f);
519 fputs("\"partitions\": [\n", f);
520 indent++;
521
522 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
523 while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
524 char *p = NULL;
525
526 ct++;
527 fput_indent(indent, f);
528 fputc('{', f);
529 if (devname)
530 p = fdisk_partname(devname, pa->partno + 1);
531 if (p) {
532 DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
533 fputs("\"node\": ", f);
534 fputs_quoted(p, f);
535 }
536
537 if (fdisk_partition_has_start(pa))
538 fprintf(f, ", \"start\": %ju", (uintmax_t)pa->start);
539 if (fdisk_partition_has_size(pa))
540 fprintf(f, ", \"size\": %ju", (uintmax_t)pa->size);
541
542 if (pa->type && fdisk_parttype_get_string(pa->type))
543 fprintf(f, ", \"type\": \"%s\"", fdisk_parttype_get_string(pa->type));
544 else if (pa->type)
545 fprintf(f, ", \"type\": \"%x\"", fdisk_parttype_get_code(pa->type));
546
547 if (pa->uuid)
548 fprintf(f, ", \"uuid\": \"%s\"", pa->uuid);
549 if (pa->name && *pa->name) {
550 fputs(", \"name\": ", f),
551 fputs_quoted(pa->name, f);
552 }
553
554 /* for MBR attr=80 means bootable */
555 if (pa->attrs) {
556 struct fdisk_label *lb = script_get_label(dp);
557
558 if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
559 fprintf(f, ", \"attrs\": \"%s\"", pa->attrs);
560 }
561 if (fdisk_partition_is_bootable(pa))
562 fprintf(f, ", \"bootable\": true");
563
564 if ((size_t)ct < fdisk_table_get_nents(dp->table))
565 fputs("},\n", f);
566 else
567 fputs("}\n", f);
568 }
569
570 indent--;
571 fput_indent(indent, f);
572 fputs("]\n", f);
573 done:
574 indent--;
575 fput_indent(indent, f);
576 fputs("}\n}\n", f);
577
578 DBG(SCRIPT, ul_debugobj(dp, "write script done"));
579 return 0;
580 }
581
582 static int write_file_sfdisk(struct fdisk_script *dp, FILE *f)
583 {
584 struct list_head *h;
585 struct fdisk_partition *pa;
586 struct fdisk_iter itr;
587 const char *devname = NULL;
588
589 assert(dp);
590 assert(f);
591
592 DBG(SCRIPT, ul_debugobj(dp, "writing sfdisk-like script to file"));
593
594 /* script headers */
595 list_for_each(h, &dp->headers) {
596 struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
597 fprintf(f, "%s: %s\n", fi->name, fi->data);
598 if (strcmp(fi->name, "device") == 0)
599 devname = fi->data;
600 }
601
602 if (!dp->table) {
603 DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
604 return 0;
605 }
606
607 DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
608
609 fputc('\n', f);
610
611 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
612 while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
613 char *p = NULL;
614
615 if (devname)
616 p = fdisk_partname(devname, pa->partno + 1);
617 if (p) {
618 DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
619 fprintf(f, "%s :", p);
620 } else
621 fprintf(f, "%zu :", pa->partno + 1);
622
623 if (fdisk_partition_has_start(pa))
624 fprintf(f, " start=%12ju", (uintmax_t)pa->start);
625 if (fdisk_partition_has_size(pa))
626 fprintf(f, ", size=%12ju", (uintmax_t)pa->size);
627
628 if (pa->type && fdisk_parttype_get_string(pa->type))
629 fprintf(f, ", type=%s", fdisk_parttype_get_string(pa->type));
630 else if (pa->type)
631 fprintf(f, ", type=%x", fdisk_parttype_get_code(pa->type));
632
633 if (pa->uuid)
634 fprintf(f, ", uuid=%s", pa->uuid);
635 if (pa->name && *pa->name)
636 fprintf(f, ", name=\"%s\"", pa->name);
637
638 /* for MBR attr=80 means bootable */
639 if (pa->attrs) {
640 struct fdisk_label *lb = script_get_label(dp);
641
642 if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
643 fprintf(f, ", attrs=\"%s\"", pa->attrs);
644 }
645 if (fdisk_partition_is_bootable(pa))
646 fprintf(f, ", bootable");
647 fputc('\n', f);
648 }
649
650 DBG(SCRIPT, ul_debugobj(dp, "write script done"));
651 return 0;
652 }
653
654 /**
655 * fdisk_script_write_file:
656 * @dp: script
657 * @f: output file
658 *
659 * Writes script @dp to the ile @f.
660 *
661 * Returns: 0 on success, <0 on error.
662 */
663 int fdisk_script_write_file(struct fdisk_script *dp, FILE *f)
664 {
665 assert(dp);
666
667 if (dp->json)
668 return write_file_json(dp, f);
669
670 return write_file_sfdisk(dp, f);
671 }
672
673 static inline int is_header_line(const char *s)
674 {
675 const char *p = strchr(s, ':');
676
677 if (!p || p == s || !*(p + 1) || strchr(s, '='))
678 return 0;
679
680 return 1;
681 }
682
683 /* parses "<name>: value", note modifies @s*/
684 static int parse_line_header(struct fdisk_script *dp, char *s)
685 {
686 int rc = -EINVAL;
687 char *name, *value;
688
689 DBG(SCRIPT, ul_debugobj(dp, " parse header '%s'", s));
690
691 if (!s || !*s)
692 return -EINVAL;
693
694 name = s;
695 value = strchr(s, ':');
696 if (!value)
697 goto done;
698 *value = '\0';
699 value++;
700
701 ltrim_whitespace((unsigned char *) name);
702 rtrim_whitespace((unsigned char *) name);
703 ltrim_whitespace((unsigned char *) value);
704 rtrim_whitespace((unsigned char *) value);
705
706 if (strcmp(name, "label") == 0) {
707 if (dp->cxt && !fdisk_get_label(dp->cxt, value))
708 goto done; /* unknown label name */
709 } else if (strcmp(name, "unit") == 0) {
710 if (strcmp(value, "sectors") != 0)
711 goto done; /* only "sectors" supported */
712 } else if (strcmp(name, "label-id") == 0
713 || strcmp(name, "device") == 0
714 || strcmp(name, "first-lba") == 0
715 || strcmp(name, "last-lba") == 0
716 || strcmp(name, "table-length") == 0) {
717 ; /* whatever is posssible */
718 } else
719 goto done; /* unknown header */
720
721 if (*name && *value)
722 rc = fdisk_script_set_header(dp, name, value);
723 done:
724 if (rc)
725 DBG(SCRIPT, ul_debugobj(dp, "header parse error: "
726 "[rc=%d, name='%s', value='%s']",
727 rc, name, value));
728 return rc;
729
730 }
731
732 /* returns zero terminated string with next token and @str is updated */
733 static char *next_token(char **str)
734 {
735 char *tk_begin = NULL,
736 *tk_end = NULL,
737 *end = NULL,
738 *p;
739 int open_quote = 0;
740
741 for (p = *str; p && *p; p++) {
742 if (!tk_begin) {
743 if (isblank(*p))
744 continue;
745 tk_begin = *p == '"' ? p + 1 : p;
746 }
747 if (*p == '"')
748 open_quote ^= 1;
749 if (open_quote)
750 continue;
751 if (isblank(*p) || *p == ',' || *p == ';' || *p == '"' )
752 tk_end = p;
753 else if (*(p + 1) == '\0')
754 tk_end = p + 1;
755 if (tk_begin && tk_end)
756 break;
757 }
758
759 if (!tk_end)
760 return NULL;
761 end = isblank(*tk_end) ? (char *) skip_blank(tk_end) : tk_end;
762 if (*end == ',' || *end == ';')
763 end++;
764
765 *tk_end = '\0';
766 *str = end;
767 return tk_begin;
768 }
769
770 static int next_number(char **s, uint64_t *num, int *power)
771 {
772 char *tk;
773 int rc = -EINVAL;
774
775 assert(num);
776 assert(s);
777
778 tk = next_token(s);
779 if (tk)
780 rc = parse_size(tk, (uintmax_t *) num, power);
781 return rc;
782 }
783
784 static int next_string(char **s, char **str)
785 {
786 char *tk;
787 int rc = -EINVAL;
788
789 assert(s);
790 assert(str);
791
792 tk = next_token(s);
793 if (tk) {
794 *str = strdup(tk);
795 rc = !*str ? -ENOMEM : 0;
796 }
797 return rc;
798 }
799
800 static int partno_from_devname(char *s)
801 {
802 int pno;
803 size_t sz;
804 char *end, *p;
805
806 sz = rtrim_whitespace((unsigned char *)s);
807 p = s + sz - 1;
808
809 while (p > s && isdigit(*(p - 1)))
810 p--;
811
812 errno = 0;
813 pno = strtol(p, &end, 10);
814 if (errno || !end || p == end)
815 return -1;
816 return pno - 1;
817 }
818
819 /* dump format
820 * <device>: start=<num>, size=<num>, type=<string>, ...
821 */
822 static int parse_line_nameval(struct fdisk_script *dp, char *s)
823 {
824 char *p, *x;
825 struct fdisk_partition *pa;
826 int rc = 0;
827 uint64_t num;
828 int pno;
829
830 assert(dp);
831 assert(s);
832
833 DBG(SCRIPT, ul_debugobj(dp, " parse script line: '%s'", s));
834
835 pa = fdisk_new_partition();
836 if (!pa)
837 return -ENOMEM;
838
839 fdisk_partition_start_follow_default(pa, 1);
840 fdisk_partition_end_follow_default(pa, 1);
841 fdisk_partition_partno_follow_default(pa, 1);
842
843 /* set partno */
844 p = strchr(s, ':');
845 x = strchr(s, '=');
846 if (p && (!x || p < x)) {
847 *p = '\0';
848 p++;
849
850 pno = partno_from_devname(s);
851 if (pno >= 0) {
852 fdisk_partition_partno_follow_default(pa, 0);
853 fdisk_partition_set_partno(pa, pno);
854 }
855 } else
856 p = s;
857
858 while (rc == 0 && p && *p) {
859
860 DBG(SCRIPT, ul_debugobj(dp, " parsing '%s'", p));
861 p = (char *) skip_blank(p);
862
863 if (!strncasecmp(p, "start=", 6)) {
864 int pow = 0;
865 p += 6;
866 rc = next_number(&p, &num, &pow);
867 if (!rc) {
868 if (pow) /* specified as <num><suffix> */
869 num /= dp->cxt->sector_size;
870 fdisk_partition_set_start(pa, num);
871 fdisk_partition_start_follow_default(pa, 0);
872 }
873 } else if (!strncasecmp(p, "size=", 5)) {
874 int pow = 0;
875
876 p += 5;
877 rc = next_number(&p, &num, &pow);
878 if (!rc) {
879 if (pow) /* specified as <num><suffix> */
880 num /= dp->cxt->sector_size;
881 else /* specified as number of sectors */
882 fdisk_partition_size_explicit(pa, 1);
883 fdisk_partition_set_size(pa, num);
884 fdisk_partition_end_follow_default(pa, 0);
885 }
886
887 } else if (!strncasecmp(p, "bootable", 8)) {
888 char *tk = next_token(&p);
889 if (strcmp(tk, "bootable") == 0)
890 pa->boot = 1;
891 else
892 rc = -EINVAL;
893
894 } else if (!strncasecmp(p, "attrs=", 6)) {
895 p += 6;
896 rc = next_string(&p, &pa->attrs);
897
898 } else if (!strncasecmp(p, "uuid=", 5)) {
899 p += 5;
900 rc = next_string(&p, &pa->uuid);
901
902 } else if (!strncasecmp(p, "name=", 5)) {
903 p += 5;
904 rc = next_string(&p, &pa->name);
905
906 } else if (!strncasecmp(p, "type=", 5) ||
907
908 !strncasecmp(p, "Id=", 3)) { /* backward compatiility */
909 char *type;
910
911 p += (*p == 'I' ? 3 : 5); /* "Id=" or "type=" */
912
913 rc = next_string(&p, &type);
914 if (rc)
915 break;
916 pa->type = fdisk_label_parse_parttype(
917 script_get_label(dp), type);
918 free(type);
919
920 if (!pa->type) {
921 rc = -EINVAL;
922 fdisk_unref_parttype(pa->type);
923 pa->type = NULL;
924 break;
925 }
926
927 } else {
928 DBG(SCRIPT, ul_debugobj(dp, "script parse error: unknown field '%s'", p));
929 rc = -EINVAL;
930 break;
931 }
932 }
933
934 if (!rc)
935 rc = fdisk_table_add_partition(dp->table, pa);
936 if (rc)
937 DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
938
939 fdisk_unref_partition(pa);
940 return rc;
941 }
942
943 /* original sfdisk supports partition types shortcuts like 'L' = Linux native
944 */
945 static struct fdisk_parttype *translate_type_shortcuts(struct fdisk_script *dp, char *str)
946 {
947 struct fdisk_label *lb;
948 const char *type = NULL;
949
950 if (strlen(str) != 1)
951 return NULL;
952
953 lb = script_get_label(dp);
954 if (!lb)
955 return NULL;
956
957 if (lb->id == FDISK_DISKLABEL_DOS) {
958 switch (*str) {
959 case 'L': /* Linux */
960 type = "83";
961 break;
962 case 'S': /* Swap */
963 type = "82";
964 break;
965 case 'E': /* Dos extended */
966 type = "05";
967 break;
968 case 'X': /* Linux extended */
969 type = "85";
970 break;
971 case 'U': /* UEFI system */
972 type = "EF";
973 break;
974 }
975 } else if (lb->id == FDISK_DISKLABEL_GPT) {
976 switch (*str) {
977 case 'L': /* Linux */
978 type = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
979 break;
980 case 'S': /* Swap */
981 type = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F";
982 break;
983 case 'H': /* Home */
984 type = "933AC7E1-2EB4-4F13-B844-0E14E2AEF915";
985 break;
986 case 'U': /* UEFI system */
987 type = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B";
988 break;
989 }
990 }
991
992 return type ? fdisk_label_parse_parttype(lb, type) : NULL;
993 }
994
995 #define TK_PLUS 1
996 #define TK_MINUS -1
997
998 #define alone_sign(_sign, _p) (_sign && (*_p == '\0' || isblank(*_p)))
999
1000 /* simple format:
1001 * <start>, <size>, <type>, <bootable>, ...
1002 */
1003 static int parse_line_valcommas(struct fdisk_script *dp, char *s)
1004 {
1005 int rc = 0;
1006 char *p = s, *str;
1007 struct fdisk_partition *pa;
1008 enum { ITEM_START, ITEM_SIZE, ITEM_TYPE, ITEM_BOOTABLE };
1009 int item = -1;
1010
1011 assert(dp);
1012 assert(s);
1013
1014 pa = fdisk_new_partition();
1015 if (!pa)
1016 return -ENOMEM;
1017
1018 fdisk_partition_start_follow_default(pa, 1);
1019 fdisk_partition_end_follow_default(pa, 1);
1020 fdisk_partition_partno_follow_default(pa, 1);
1021
1022 while (rc == 0 && p && *p) {
1023 uint64_t num;
1024 char *begin;
1025 int sign = 0;
1026
1027 p = (char *) skip_blank(p);
1028 item++;
1029
1030 if (item != ITEM_BOOTABLE) {
1031 sign = *p == '-' ? TK_MINUS : *p == '+' ? TK_PLUS : 0;
1032 if (sign)
1033 p++;
1034 }
1035
1036 DBG(SCRIPT, ul_debugobj(dp, " parsing item %d ('%s')", item, p));
1037 begin = p;
1038
1039 switch (item) {
1040 case ITEM_START:
1041 if (*p == ',' || *p == ';' || alone_sign(sign, p))
1042 fdisk_partition_start_follow_default(pa, 1);
1043 else {
1044 int pow = 0;
1045
1046 rc = next_number(&p, &num, &pow);
1047 if (!rc) {
1048 if (pow) /* specified as <num><suffix> */
1049 num /= dp->cxt->sector_size;
1050 fdisk_partition_set_start(pa, num);
1051 pa->movestart = sign == TK_MINUS ? FDISK_MOVE_DOWN :
1052 sign == TK_PLUS ? FDISK_MOVE_UP :
1053 FDISK_MOVE_NONE;
1054 }
1055 fdisk_partition_start_follow_default(pa, 0);
1056 }
1057 break;
1058 case ITEM_SIZE:
1059 if (*p == ',' || *p == ';' || alone_sign(sign, p)) {
1060 fdisk_partition_end_follow_default(pa, 1);
1061 if (sign == TK_PLUS)
1062 /* alone '+' means use all possible space, elone '-' means nothing */
1063 pa->resize = FDISK_RESIZE_ENLARGE;
1064 } else {
1065 int pow = 0;
1066 rc = next_number(&p, &num, &pow);
1067 if (!rc) {
1068 if (pow) /* specified as <size><suffix> */
1069 num /= dp->cxt->sector_size;
1070 else /* specified as number of sectors */
1071 fdisk_partition_size_explicit(pa, 1);
1072 fdisk_partition_set_size(pa, num);
1073 pa->resize = sign == TK_MINUS ? FDISK_RESIZE_REDUCE :
1074 sign == TK_PLUS ? FDISK_RESIZE_ENLARGE :
1075 FDISK_RESIZE_NONE;
1076 }
1077 fdisk_partition_end_follow_default(pa, 0);
1078 }
1079 break;
1080 case ITEM_TYPE:
1081 if (*p == ',' || *p == ';' || alone_sign(sign, p))
1082 break; /* use default type */
1083
1084 rc = next_string(&p, &str);
1085 if (rc)
1086 break;
1087
1088 pa->type = translate_type_shortcuts(dp, str);
1089 if (!pa->type)
1090 pa->type = fdisk_label_parse_parttype(
1091 script_get_label(dp), str);
1092 free(str);
1093
1094 if (!pa->type) {
1095 rc = -EINVAL;
1096 fdisk_unref_parttype(pa->type);
1097 pa->type = NULL;
1098 break;
1099 }
1100 break;
1101 case ITEM_BOOTABLE:
1102 if (*p == ',' || *p == ';')
1103 break;
1104 else {
1105 char *tk = next_token(&p);
1106 if (tk && *tk == '*' && *(tk + 1) == '\0')
1107 pa->boot = 1;
1108 else if (tk && *tk == '-' && *(tk + 1) == '\0')
1109 pa->boot = 0;
1110 else if (tk && *tk == '+' && *(tk + 1) == '\0')
1111 pa->boot = 1;
1112 else
1113 rc = -EINVAL;
1114 }
1115 break;
1116 default:
1117 break;
1118 }
1119
1120 if (begin == p)
1121 p++;
1122 }
1123
1124 if (!rc)
1125 rc = fdisk_table_add_partition(dp->table, pa);
1126 if (rc)
1127 DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
1128
1129 fdisk_unref_partition(pa);
1130 return rc;
1131 }
1132
1133 /* modifies @s ! */
1134 static int fdisk_script_read_buffer(struct fdisk_script *dp, char *s)
1135 {
1136 int rc = 0;
1137
1138 assert(dp);
1139 assert(s);
1140
1141 DBG(SCRIPT, ul_debugobj(dp, " parsing buffer"));
1142
1143 s = (char *) skip_blank(s);
1144 if (!s || !*s)
1145 return 0; /* nothing baby, ignore */
1146
1147 if (!dp->table) {
1148 dp->table = fdisk_new_table();
1149 if (!dp->table)
1150 return -ENOMEM;
1151 }
1152
1153 /* parse header lines only if no partition specified yet */
1154 if (fdisk_table_is_empty(dp->table) && is_header_line(s))
1155 rc = parse_line_header(dp, s);
1156
1157 /* parse script format */
1158 else if (strchr(s, '='))
1159 rc = parse_line_nameval(dp, s);
1160
1161 /* parse simple <value>, ... format */
1162 else
1163 rc = parse_line_valcommas(dp, s);
1164
1165 if (rc)
1166 DBG(SCRIPT, ul_debugobj(dp, "%zu: parse error [rc=%d]",
1167 dp->nlines, rc));
1168 return rc;
1169 }
1170
1171 /**
1172 * fdisk_script_set_fgets:
1173 * @dp: script
1174 * @fn_fgets: callback function
1175 *
1176 * The library uses fgets() function to read the next line from the script.
1177 * This default maybe overrided to another function. Note that the function has
1178 * to return the line terminated by \n (for example readline(3) removes \n).
1179 *
1180 * Return: 0 on success, <0 on error
1181 */
1182 int fdisk_script_set_fgets(struct fdisk_script *dp,
1183 char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *))
1184 {
1185 assert(dp);
1186
1187 dp->fn_fgets = fn_fgets;
1188 return 0;
1189 }
1190
1191 /**
1192 * fdisk_script_read_line:
1193 * @dp: script
1194 * @f: file
1195 * @buf: buffer to store one line of the file
1196 * @bufsz: buffer size
1197 *
1198 * Reads next line into dump.
1199 *
1200 * Returns: 0 on success, <0 on error, 1 when nothing to read.
1201 */
1202 int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz)
1203 {
1204 char *s;
1205
1206 assert(dp);
1207 assert(f);
1208
1209 DBG(SCRIPT, ul_debugobj(dp, " parsing line %zu", dp->nlines));
1210
1211 /* read the next non-blank non-comment line */
1212 do {
1213 if (dp->fn_fgets) {
1214 if (dp->fn_fgets(dp, buf, bufsz, f) == NULL)
1215 return 1;
1216 } else if (fgets(buf, bufsz, f) == NULL)
1217 return 1;
1218
1219 dp->nlines++;
1220 s = strchr(buf, '\n');
1221 if (!s) {
1222 /* Missing final newline? Otherwise an extremely */
1223 /* long line - assume file was corrupted */
1224 if (feof(f)) {
1225 DBG(SCRIPT, ul_debugobj(dp, "no final newline"));
1226 s = strchr(buf, '\0');
1227 } else {
1228 DBG(SCRIPT, ul_debugobj(dp,
1229 "%zu: missing newline at line", dp->nlines));
1230 return -EINVAL;
1231 }
1232 }
1233
1234 *s = '\0';
1235 if (--s >= buf && *s == '\r')
1236 *s = '\0';
1237 s = (char *) skip_blank(buf);
1238 } while (*s == '\0' || *s == '#');
1239
1240 return fdisk_script_read_buffer(dp, s);
1241 }
1242
1243
1244 /**
1245 * fdisk_script_read_file:
1246 * @dp: script
1247 * @f: input file
1248 *
1249 * Reads file @f into script @dp.
1250 *
1251 * Returns: 0 on success, <0 on error.
1252 */
1253 int fdisk_script_read_file(struct fdisk_script *dp, FILE *f)
1254 {
1255 char buf[BUFSIZ];
1256 int rc = 1;
1257
1258 assert(dp);
1259 assert(f);
1260
1261 DBG(SCRIPT, ul_debugobj(dp, "parsing file"));
1262
1263 while (!feof(f)) {
1264 rc = fdisk_script_read_line(dp, f, buf, sizeof(buf));
1265 if (rc)
1266 break;
1267 }
1268
1269 if (rc == 1)
1270 rc = 0; /* end of file */
1271
1272 DBG(SCRIPT, ul_debugobj(dp, "parsing file done [rc=%d]", rc));
1273 return rc;
1274 }
1275
1276 /**
1277 * fdisk_set_script:
1278 * @cxt: context
1279 * @dp: script (or NULL to remove previous reference)
1280 *
1281 * Sets reference to the @dp script and remove reference to the previously used
1282 * script.
1283 *
1284 * The script headers might be used by label drivers to overwrite
1285 * built-in defaults (for example disk label Id) and label driver might
1286 * optimize the default semantic to be more usable for scripts (for example to
1287 * not ask for primary/logical/extended partition type).
1288 *
1289 * Note that script also contains reference to the fdisk context (see
1290 * fdisk_new_script()). This context may be completely independent on
1291 * context used for fdisk_set_script().
1292 *
1293 * Returns: <0 on error, 0 on success.
1294 */
1295 int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp)
1296 {
1297 assert(cxt);
1298
1299 /* unref old */
1300 if (cxt->script)
1301 fdisk_unref_script(cxt->script);
1302
1303 /* ref new */
1304 cxt->script = dp;
1305 if (cxt->script) {
1306 DBG(CXT, ul_debugobj(cxt, "setting reference to script %p", cxt->script));
1307 fdisk_ref_script(cxt->script);
1308 }
1309
1310 return 0;
1311 }
1312
1313 /**
1314 * fdisk_get_script:
1315 * @cxt: context
1316 *
1317 * Returns: the current script or NULL.
1318 */
1319 struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt)
1320 {
1321 assert(cxt);
1322 return cxt->script;
1323 }
1324
1325 /**
1326 * fdisk_apply_script_headers:
1327 * @cxt: context
1328 * @dp: script
1329 *
1330 * Associte context @cxt with script @dp and creates a new empty disklabel.
1331 *
1332 * Returns: 0 on success, <0 on error.
1333 */
1334 int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp)
1335 {
1336 const char *name;
1337 const char *str;
1338 int rc;
1339
1340 assert(cxt);
1341 assert(dp);
1342
1343 DBG(SCRIPT, ul_debugobj(dp, "applying script headers"));
1344 fdisk_set_script(cxt, dp);
1345
1346 /* create empty label */
1347 name = fdisk_script_get_header(dp, "label");
1348 if (!name)
1349 return -EINVAL;
1350
1351 rc = fdisk_create_disklabel(cxt, name);
1352 if (rc)
1353 return rc;
1354
1355 str = fdisk_script_get_header(dp, "table-length");
1356 if (str) {
1357 uintmax_t sz;
1358
1359 rc = parse_size(str, &sz, NULL);
1360 if (rc == 0)
1361 rc = fdisk_gpt_set_npartitions(cxt, sz);
1362 }
1363
1364 return rc;
1365 }
1366
1367 /**
1368 * fdisk_apply_script:
1369 * @cxt: context
1370 * @dp: script
1371 *
1372 * This function creates a new disklabel and partition within context @cxt. You
1373 * have to call fdisk_write_disklabel() to apply changes to the device.
1374 *
1375 * Returns: 0 on error, <0 on error.
1376 */
1377 int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp)
1378 {
1379 int rc;
1380 struct fdisk_script *old;
1381
1382 assert(dp);
1383 assert(cxt);
1384
1385 DBG(CXT, ul_debugobj(cxt, "applying script %p", dp));
1386
1387 old = fdisk_get_script(cxt);
1388 fdisk_ref_script(old);
1389
1390 /* create empty disk label */
1391 rc = fdisk_apply_script_headers(cxt, dp);
1392
1393 /* create partitions */
1394 if (!rc && dp->table)
1395 rc = fdisk_apply_table(cxt, dp->table);
1396
1397 fdisk_set_script(cxt, old);
1398 fdisk_unref_script(old);
1399
1400 DBG(CXT, ul_debugobj(cxt, "script done [rc=%d]", rc));
1401 return rc;
1402 }
1403
1404 #ifdef TEST_PROGRAM
1405 static int test_dump(struct fdisk_test *ts, int argc, char *argv[])
1406 {
1407 char *devname = argv[1];
1408 struct fdisk_context *cxt;
1409 struct fdisk_script *dp;
1410
1411 cxt = fdisk_new_context();
1412 fdisk_assign_device(cxt, devname, 1);
1413
1414 dp = fdisk_new_script(cxt);
1415 fdisk_script_read_context(dp, NULL);
1416
1417 fdisk_script_write_file(dp, stdout);
1418 fdisk_unref_script(dp);
1419 fdisk_unref_context(cxt);
1420
1421 return 0;
1422 }
1423
1424 static int test_read(struct fdisk_test *ts, int argc, char *argv[])
1425 {
1426 char *filename = argv[1];
1427 struct fdisk_script *dp;
1428 struct fdisk_context *cxt;
1429 FILE *f;
1430
1431 if (!(f = fopen(filename, "r")))
1432 err(EXIT_FAILURE, "%s: cannot open", filename);
1433
1434 cxt = fdisk_new_context();
1435 dp = fdisk_new_script(cxt);
1436
1437 fdisk_script_read_file(dp, f);
1438 fclose(f);
1439
1440 fdisk_script_write_file(dp, stdout);
1441 fdisk_unref_script(dp);
1442 fdisk_unref_context(cxt);
1443
1444 return 0;
1445 }
1446
1447 static int test_stdin(struct fdisk_test *ts, int argc, char *argv[])
1448 {
1449 char buf[BUFSIZ];
1450 struct fdisk_script *dp;
1451 struct fdisk_context *cxt;
1452 int rc = 0;
1453
1454 cxt = fdisk_new_context();
1455 dp = fdisk_new_script(cxt);
1456 fdisk_script_set_header(dp, "label", "dos");
1457
1458 printf("<start>, <size>, <type>, <bootable: *|->\n");
1459 do {
1460 struct fdisk_partition *pa;
1461 size_t n = fdisk_table_get_nents(dp->table);
1462
1463 printf(" #%zu :\n", n + 1);
1464 rc = fdisk_script_read_line(dp, stdin, buf, sizeof(buf));
1465
1466 if (rc == 0) {
1467 pa = fdisk_table_get_partition(dp->table, n);
1468 printf(" #%zu %12ju %12ju\n", n + 1,
1469 (uintmax_t)fdisk_partition_get_start(pa),
1470 (uintmax_t)fdisk_partition_get_size(pa));
1471 }
1472 } while (rc == 0);
1473
1474 if (!rc)
1475 fdisk_script_write_file(dp, stdout);
1476 fdisk_unref_script(dp);
1477 fdisk_unref_context(cxt);
1478
1479 return rc;
1480 }
1481
1482 static int test_apply(struct fdisk_test *ts, int argc, char *argv[])
1483 {
1484 char *devname = argv[1], *scriptname = argv[2];
1485 struct fdisk_context *cxt;
1486 struct fdisk_script *dp = NULL;
1487 struct fdisk_table *tb = NULL;
1488 struct fdisk_iter *itr = NULL;
1489 struct fdisk_partition *pa = NULL;
1490 int rc;
1491
1492 cxt = fdisk_new_context();
1493 fdisk_assign_device(cxt, devname, 0);
1494
1495 dp = fdisk_new_script_from_file(cxt, scriptname);
1496 if (!dp)
1497 return -errno;
1498
1499 rc = fdisk_apply_script(cxt, dp);
1500 if (rc)
1501 goto done;
1502 fdisk_unref_script(dp);
1503
1504 /* list result */
1505 fdisk_list_disklabel(cxt);
1506 fdisk_get_partitions(cxt, &tb);
1507
1508 itr = fdisk_new_iter(FDISK_ITER_FORWARD);
1509 while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
1510 printf(" #%zu %12ju %12ju\n", fdisk_partition_get_partno(pa),
1511 (uintmax_t)fdisk_partition_get_start(pa),
1512 (uintmax_t)fdisk_partition_get_size(pa));
1513 }
1514
1515 done:
1516 fdisk_free_iter(itr);
1517 fdisk_unref_table(tb);
1518
1519 /*fdisk_write_disklabel(cxt);*/
1520 fdisk_unref_context(cxt);
1521 return 0;
1522 }
1523
1524 int main(int argc, char *argv[])
1525 {
1526 struct fdisk_test tss[] = {
1527 { "--dump", test_dump, "<device> dump PT as script" },
1528 { "--read", test_read, "<file> read PT script from file" },
1529 { "--apply", test_apply, "<device> <file> try apply script from file to device" },
1530 { "--stdin", test_stdin, " read input like sfdisk" },
1531 { NULL }
1532 };
1533
1534 return fdisk_run_test(tss, argc, argv);
1535 }
1536
1537 #endif