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