X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=daemon%2Fparted.c;h=2f928b05b5e50c7d4bce90d4a593b54c5f4028bd;hp=99417c2355e221549af93edabe139ba5e2df4e60;hb=6280ac9b987c14f89749b4b4fdfec5a647567432;hpb=9e0b31a2af26b8d58a44dd80993a5e73d4942307 diff --git a/daemon/parted.c b/daemon/parted.c index 99417c2..2f928b0 100644 --- a/daemon/parted.c +++ b/daemon/parted.c @@ -55,7 +55,7 @@ recover_blkrrpart (const char *device, const char *err) return 0; } -#define RUN_PARTED(device,...) \ +#define RUN_PARTED(error,device,...) \ do { \ int r; \ char *err; \ @@ -66,7 +66,7 @@ recover_blkrrpart (const char *device, const char *err) if (recover_blkrrpart ((device), err) == -1) { \ reply_with_error ("%s: parted: %s: %s", __func__, (device), err); \ free (err); \ - return -1; \ + error; \ } \ } \ \ @@ -107,7 +107,7 @@ do_part_init (const char *device, const char *parttype) return -1; } - RUN_PARTED (device, "mklabel", parttype, NULL); + RUN_PARTED (return -1, device, "mklabel", parttype, NULL); udev_settle (); @@ -151,7 +151,7 @@ do_part_add (const char *device, const char *prlogex, * name_ to prlogex, eg. "primary". I would essentially describe * this as a bug in the parted mkpart command. */ - RUN_PARTED (device, "mkpart", prlogex, startstr, endstr, NULL); + RUN_PARTED (return -1, device, "mkpart", prlogex, startstr, endstr, NULL); udev_settle (); @@ -159,6 +159,23 @@ do_part_add (const char *device, const char *prlogex, } int +do_part_del (const char *device, int partnum) +{ + if (partnum <= 0) { + reply_with_error ("partition number must be >= 1"); + return -1; + } + + char partnum_str[16]; + snprintf (partnum_str, sizeof partnum_str, "%d", partnum); + + RUN_PARTED (return -1, device, "rm", partnum_str, NULL); + + udev_settle (); + return 0; +} + +int do_part_disk (const char *device, const char *parttype) { const char *startstr; @@ -183,7 +200,8 @@ do_part_disk (const char *device, const char *parttype) endstr = "-1s"; } - RUN_PARTED (device, + RUN_PARTED (return -1, + device, "mklabel", parttype, /* See comment about about the parted mkpart command. */ "mkpart", STREQ (parttype, "gpt") ? "p1" : "primary", @@ -197,11 +215,17 @@ do_part_disk (const char *device, const char *parttype) int do_part_set_bootable (const char *device, int partnum, int bootable) { + if (partnum <= 0) { + reply_with_error ("partition number must be >= 1"); + return -1; + } + char partstr[16]; snprintf (partstr, sizeof partstr, "%d", partnum); - RUN_PARTED (device, "set", partstr, "boot", bootable ? "on" : "off", NULL); + RUN_PARTED (return -1, + device, "set", partstr, "boot", bootable ? "on" : "off", NULL); udev_settle (); @@ -211,27 +235,95 @@ do_part_set_bootable (const char *device, int partnum, int bootable) int do_part_set_name (const char *device, int partnum, const char *name) { + if (partnum <= 0) { + reply_with_error ("partition number must be >= 1"); + return -1; + } + char partstr[16]; snprintf (partstr, sizeof partstr, "%d", partnum); - RUN_PARTED (device, "name", partstr, name, NULL); + RUN_PARTED (return -1, device, "name", partstr, name, NULL); udev_settle (); return 0; } -static char ** -print_partition_table (const char *device) +/* Return the nth field from a string of ':'/';'-delimited strings. + * Useful for parsing the return value from 'parted -m'. + */ +static char * +get_table_field (const char *line, int n) +{ + const char *p = line; + + while (*p && n > 0) { + p += strcspn (p, ":;") + 1; + n--; + } + + if (n > 0) { + reply_with_error ("not enough fields in output of parted print command: %s", + line); + return NULL; + } + + size_t len = strcspn (p, ":;"); + char *q = strndup (p, len); + if (q == NULL) { + reply_with_perror ("strndup"); + return NULL; + } + + return q; +} + +/* RHEL 5 parted doesn't have the -m (machine readable) option so we + * must do a lot more work to parse the output in + * print_partition_table below. Test for this option the first time + * this function is called. + */ +static int +test_parted_m_opt (void) +{ + static int result = -1; + + if (result >= 0) + return result; + + if (verbose) + fprintf (stderr, "Testing if this parted supports '-m' option.\n"); + + char *err = NULL; + int r = commandr (NULL, &err, "parted", "-s", "-m", "/dev/null", NULL); + if (r == -1) { + /* Test failed, eg. missing or completely unusable parted binary. */ + reply_with_error ("could not run 'parted' command"); + return -1; + } + + if (err && strstr (err, "invalid option -- m")) + return result = 0; + + return result = 1; +} + +static char * +print_partition_table (const char *device, int parted_has_m_opt) { char *out, *err; int r; - char **lines; - r = command (&out, &err, "parted", "-m", "--", device, - "unit", "b", - "print", NULL); + if (parted_has_m_opt) + r = command (&out, &err, "parted", "-m", "--", device, + "unit", "b", + "print", NULL); + else + r = command (&out, &err, "parted", "-s", "--", device, + "unit", "b", + "print", NULL); if (r == -1) { reply_with_error ("parted print: %s: %s", device, /* Hack for parted 1.x which sends errors to stdout. */ @@ -242,105 +334,198 @@ print_partition_table (const char *device) } free (err); - lines = split_lines (out); - free (out); - - if (!lines) - return NULL; - - if (lines[0] == NULL || STRNEQ (lines[0], "BYT;")) { - reply_with_error ("unknown signature, expected \"BYT;\" as first line of the output: %s", - lines[0] ? lines[0] : "(signature was null)"); - free_strings (lines); - return NULL; - } - - if (lines[1] == NULL) { - reply_with_error ("parted didn't return a line describing the device"); - free_strings (lines); - return NULL; - } + if (verbose) + fprintf (stderr, "parted output:\n%s\n", out); - return lines; + return out; } char * do_part_get_parttype (const char *device) { - char **lines; - char *r; - - lines = print_partition_table (device); - if (!lines) + int parted_has_m_opt = test_parted_m_opt (); + if (parted_has_m_opt == -1) return NULL; - /* lines[1] is something like: - * "/dev/sda:1953525168s:scsi:512:512:msdos:ATA Hitachi HDT72101;" - */ - if (strtok (lines[1], ":") == NULL /* device */ - || strtok (NULL, ":") == NULL /* size */ - || strtok (NULL, ":") == NULL /* transport */ - || strtok (NULL, ":") == NULL /* sector size */ - || strtok (NULL, ":") == NULL /* physical sector size */ - || (r = strtok (NULL, ":")) == NULL /* return value */ - ) { - reply_with_error ("too few fields in output from parted print command: %s", lines[1]); - free_strings (lines); + char *out = print_partition_table (device, parted_has_m_opt); + if (!out) return NULL; - } - r = strdup (r); - if (!r) { - reply_with_perror ("strdup"); + if (parted_has_m_opt) { + /* New-style parsing using the "machine-readable" format from + * 'parted -m'. + */ + char **lines = split_lines (out); + free (out); + + if (!lines) + return NULL; + + if (lines[0] == NULL || STRNEQ (lines[0], "BYT;")) { + reply_with_error ("unknown signature, expected \"BYT;\" as first line of the output: %s", + lines[0] ? lines[0] : "(signature was null)"); + free_strings (lines); + return NULL; + } + + if (lines[1] == NULL) { + reply_with_error ("parted didn't return a line describing the device"); + free_strings (lines); + return NULL; + } + + /* lines[1] is something like: + * "/dev/sda:1953525168s:scsi:512:512:msdos:ATA Hitachi HDT72101;" + */ + char *r = get_table_field (lines[1], 5); + if (r == NULL) { + free_strings (lines); + return NULL; + } + free_strings (lines); - return NULL; + return r; } + else { + /* Old-style. Look for "\nPartition Table: \n". */ + char *p = strstr (out, "\nPartition Table: "); + if (!p) { + reply_with_error ("parted didn't return Partition Table line"); + free (out); + return NULL; + } - free_strings (lines); + p += 18; + char *q = strchr (p, '\n'); + if (!q) { + reply_with_error ("parted Partition Table has no end of line char"); + free (out); + return NULL; + } - return r; + *q = '\0'; + + p = strdup (p); + free (out); + if (!p) { + reply_with_perror ("strdup"); + return NULL; + } + + return p; /* caller frees */ + } } guestfs_int_partition_list * do_part_list (const char *device) { - char **lines; - size_t row, nr_rows, i; - guestfs_int_partition_list *r; + int parted_has_m_opt = test_parted_m_opt (); + if (parted_has_m_opt == -1) + return NULL; + + char *out = print_partition_table (device, parted_has_m_opt); + if (!out) + return NULL; + + char **lines = split_lines (out); + free (out); - lines = print_partition_table (device); if (!lines) return NULL; - /* lines[0] is "BYT;", lines[1] is the device line which we ignore, - * lines[2..] are the partitions themselves. Count how many. - */ - nr_rows = 0; - for (row = 2; lines[row] != NULL; ++row) - ++nr_rows; - - r = malloc (sizeof *r); - if (r == NULL) { - reply_with_perror ("malloc"); - goto error1; - } - r->guestfs_int_partition_list_len = nr_rows; - r->guestfs_int_partition_list_val = - malloc (nr_rows * sizeof (guestfs_int_partition)); - if (r->guestfs_int_partition_list_val == NULL) { - reply_with_perror ("malloc"); - goto error2; + guestfs_int_partition_list *r; + + if (parted_has_m_opt) { + /* New-style parsing using the "machine-readable" format from + * 'parted -m'. + * + * lines[0] is "BYT;", lines[1] is the device line which we ignore, + * lines[2..] are the partitions themselves. Count how many. + */ + size_t nr_rows = 0, row; + for (row = 2; lines[row] != NULL; ++row) + ++nr_rows; + + r = malloc (sizeof *r); + if (r == NULL) { + reply_with_perror ("malloc"); + goto error1; + } + r->guestfs_int_partition_list_len = nr_rows; + r->guestfs_int_partition_list_val = + malloc (nr_rows * sizeof (guestfs_int_partition)); + if (r->guestfs_int_partition_list_val == NULL) { + reply_with_perror ("malloc"); + goto error2; + } + + /* Now parse the lines. */ + size_t i; + for (i = 0, row = 2; lines[row] != NULL; ++i, ++row) { + if (sscanf (lines[row], "%d:%" SCNi64 "B:%" SCNi64 "B:%" SCNi64 "B", + &r->guestfs_int_partition_list_val[i].part_num, + &r->guestfs_int_partition_list_val[i].part_start, + &r->guestfs_int_partition_list_val[i].part_end, + &r->guestfs_int_partition_list_val[i].part_size) != 4) { + reply_with_error ("could not parse row from output of parted print command: %s", lines[row]); + goto error3; + } + } } + else { + /* Old-style. Start at the line following "^Number", up to the + * next blank line. + */ + size_t start = 0, end = 0, row; + + for (row = 0; lines[row] != NULL; ++row) + if (STRPREFIX (lines[row], "Number")) { + start = row+1; + break; + } + + if (start == 0) { + reply_with_error ("parted output has no \"Number\" line"); + goto error1; + } + + for (row = start; lines[row] != NULL; ++row) + if (STREQ (lines[row], "")) { + end = row; + break; + } + + if (end == 0) { + reply_with_error ("parted output has no blank after end of table"); + goto error1; + } + + size_t nr_rows = end - start; - /* Now parse the lines. */ - for (i = 0, row = 2; lines[row] != NULL; ++i, ++row) { - if (sscanf (lines[row], "%d:%" SCNi64 "B:%" SCNi64 "B:%" SCNi64 "B", - &r->guestfs_int_partition_list_val[i].part_num, - &r->guestfs_int_partition_list_val[i].part_start, - &r->guestfs_int_partition_list_val[i].part_end, - &r->guestfs_int_partition_list_val[i].part_size) != 4) { - reply_with_error ("could not parse row from output of parted print command: %s", lines[row]); - goto error3; + r = malloc (sizeof *r); + if (r == NULL) { + reply_with_perror ("malloc"); + goto error1; + } + r->guestfs_int_partition_list_len = nr_rows; + r->guestfs_int_partition_list_val = + malloc (nr_rows * sizeof (guestfs_int_partition)); + if (r->guestfs_int_partition_list_val == NULL) { + reply_with_perror ("malloc"); + goto error2; + } + + /* Now parse the lines. */ + size_t i; + for (i = 0, row = start; row < end; ++i, ++row) { + if (sscanf (lines[row], " %d %" SCNi64 "B %" SCNi64 "B %" SCNi64 "B", + &r->guestfs_int_partition_list_val[i].part_num, + &r->guestfs_int_partition_list_val[i].part_start, + &r->guestfs_int_partition_list_val[i].part_end, + &r->guestfs_int_partition_list_val[i].part_size) != 4) { + reply_with_error ("could not parse row from output of parted print command: %s", lines[row]); + goto error3; + } } } @@ -355,3 +540,166 @@ do_part_list (const char *device) free_strings (lines); return NULL; } + +int +do_part_get_bootable (const char *device, int partnum) +{ + if (partnum <= 0) { + reply_with_error ("partition number must be >= 1"); + return -1; + } + + int parted_has_m_opt = test_parted_m_opt (); + if (parted_has_m_opt == -1) + return -1; + + char *out = print_partition_table (device, parted_has_m_opt); + if (!out) + return -1; + + char **lines = split_lines (out); + free (out); + + if (!lines) + return -1; + + if (parted_has_m_opt) { + /* New-style parsing using the "machine-readable" format from + * 'parted -m'. + * + * We want lines[1+partnum]. + */ + if (count_strings (lines) < (size_t) 1+partnum) { + reply_with_error ("partition number out of range: %d", partnum); + free_strings (lines); + return -1; + } + + char *boot = get_table_field (lines[1+partnum], 6); + if (boot == NULL) { + free_strings (lines); + return -1; + } + + int r = STREQ (boot, "boot"); + + free (boot); + free_strings (lines); + + return r; + } + else { + /* Old-style: First look for the line matching "^Number". */ + size_t start = 0, header, row; + + for (row = 0; lines[row] != NULL; ++row) + if (STRPREFIX (lines[row], "Number")) { + start = row+1; + header = row; + break; + } + + if (start == 0) { + reply_with_error ("parted output has no \"Number\" line"); + free_strings (lines); + return -1; + } + + /* Now we have to look at the column number of the "Flags" field. + * This is because parted's output has no way to represent a + * missing field except as whitespace, so we cannot just count + * fields from the left. eg. The "File system" field is often + * missing in the output. + */ + char *p = strstr (lines[header], "Flags"); + if (!p) { + reply_with_error ("parted output has no \"Flags\" field"); + free_strings (lines); + return -1; + } + size_t col = p - lines[header]; + + /* Look for the line corresponding to this partition number. */ + row = start + partnum - 1; + if (row >= count_strings (lines) || !STRPREFIX (lines[row], " ")) { + reply_with_error ("partition number out of range: %d", partnum); + free_strings (lines); + return -1; + } + + int r = STRPREFIX (&lines[row][col], "boot"); + free_strings (lines); + return r; + } +} + +/* Currently we use sfdisk for getting and setting the ID byte. In + * future, extend parted to provide this functionality. As a result + * of using sfdisk, this won't work for non-MBR-style partitions, but + * that limitation is noted in the documentation and we can extend it + * later without breaking the ABI. + */ +int +do_part_get_mbr_id (const char *device, int partnum) +{ + if (partnum <= 0) { + reply_with_error ("partition number must be >= 1"); + return -1; + } + + char partnum_str[16]; + snprintf (partnum_str, sizeof partnum_str, "%d", partnum); + + char *out, *err; + int r; + + r = command (&out, &err, "sfdisk", "--print-id", device, partnum_str, NULL); + if (r == -1) { + reply_with_error ("sfdisk --print-id: %s", err); + free (out); + free (err); + return -1; + } + + free (err); + + /* It's printed in hex ... */ + int id; + if (sscanf (out, "%x", &id) != 1) { + reply_with_error ("sfdisk --print-id: cannot parse output: %s", out); + free (out); + return -1; + } + + free (out); + return id; +} + +int +do_part_set_mbr_id (const char *device, int partnum, int idbyte) +{ + if (partnum <= 0) { + reply_with_error ("partition number must be >= 1"); + return -1; + } + + char partnum_str[16]; + snprintf (partnum_str, sizeof partnum_str, "%d", partnum); + + char idbyte_str[16]; + snprintf (idbyte_str, sizeof partnum_str, "%x", idbyte); /* NB: hex */ + + char *err; + int r; + + r = command (NULL, &err, "sfdisk", + "--change-id", device, partnum_str, idbyte_str, NULL); + if (r == -1) { + reply_with_error ("sfdisk --change-id: %s", err); + free (err); + return -1; + } + + free (err); + return 0; +}