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