X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=daemon%2Fparted.c;h=068384ef51b5265fcbed892349e1729ab2da94db;hp=e0183fb4dcd47be14f4a737129a396342c1e1fad;hb=4d900cdac8258daa2e99c6ceb2a4985154e94150;hpb=b1e1ca2f74a921b3f784537d59c617df29ea1d60 diff --git a/daemon/parted.c b/daemon/parted.c index e0183fb..068384e 100644 --- a/daemon/parted.c +++ b/daemon/parted.c @@ -43,10 +43,10 @@ recover_blkrrpart (const char *device, const char *err) int r; if (!strstr (err, - "Error informing the kernel about modifications to partition")) + "Error informing the kernel about modifications to partition")) return -1; - r = command (NULL, NULL, "/sbin/blockdev", "--rereadpt", device, NULL); + r = command (NULL, NULL, "blockdev", "--rereadpt", device, NULL); if (r == -1) return -1; @@ -55,44 +55,44 @@ recover_blkrrpart (const char *device, const char *err) return 0; } -#define RUN_PARTED(device,...) \ - do { \ - int r; \ - char *err; \ - \ - r = commandf (NULL, &err, COMMAND_FLAG_FOLD_STDOUT_ON_STDERR, \ - "/sbin/parted", "-s", "--", (device), __VA_ARGS__); \ - if (r == -1) { \ - if (recover_blkrrpart ((device), err) == -1) { \ - reply_with_error ("%s: parted: %s: %s", __func__, (device), err); \ - free (err); \ - return -1; \ - } \ - } \ - \ - free (err); \ +#define RUN_PARTED(error,device,...) \ + do { \ + int r; \ + char *err; \ + \ + r = commandf (NULL, &err, COMMAND_FLAG_FOLD_STDOUT_ON_STDERR, \ + "parted", "-s", "--", (device), __VA_ARGS__); \ + if (r == -1) { \ + if (recover_blkrrpart ((device), err) == -1) { \ + reply_with_error ("%s: parted: %s: %s", __func__, (device), err); \ + free (err); \ + error; \ + } \ + } \ + \ + free (err); \ } while (0) static const char * check_parttype (const char *parttype) { /* Check and translate parttype. */ - if (strcmp (parttype, "aix") == 0 || - strcmp (parttype, "amiga") == 0 || - strcmp (parttype, "bsd") == 0 || - strcmp (parttype, "dasd") == 0 || - strcmp (parttype, "dvh") == 0 || - strcmp (parttype, "gpt") == 0 || - strcmp (parttype, "mac") == 0 || - strcmp (parttype, "msdos") == 0 || - strcmp (parttype, "pc98") == 0 || - strcmp (parttype, "sun") == 0) + if (STREQ (parttype, "aix") || + STREQ (parttype, "amiga") || + STREQ (parttype, "bsd") || + STREQ (parttype, "dasd") || + STREQ (parttype, "dvh") || + STREQ (parttype, "gpt") || + STREQ (parttype, "mac") || + STREQ (parttype, "msdos") || + STREQ (parttype, "pc98") || + STREQ (parttype, "sun")) return parttype; - else if (strcmp (parttype, "rdb") == 0) + else if (STREQ (parttype, "rdb")) return "amiga"; - else if (strcmp (parttype, "efi") == 0) + else if (STREQ (parttype, "efi")) return "gpt"; - else if (strcmp (parttype, "mbr") == 0) + else if (STREQ (parttype, "mbr")) return "msdos"; else return NULL; @@ -103,11 +103,11 @@ do_part_init (const char *device, const char *parttype) { parttype = check_parttype (parttype); if (!parttype) { - reply_with_error ("part-init: unknown partition type: common choices are \"gpt\" and \"msdos\""); + reply_with_error ("unknown partition type: common choices are \"gpt\" and \"msdos\""); return -1; } - RUN_PARTED (device, "mklabel", parttype, NULL); + RUN_PARTED (return -1, device, "mklabel", parttype, NULL); udev_settle (); @@ -122,23 +122,23 @@ do_part_add (const char *device, const char *prlogex, char endstr[32]; /* Check and translate prlogex. */ - if (strcmp (prlogex, "primary") == 0 || - strcmp (prlogex, "logical") == 0 || - strcmp (prlogex, "extended") == 0) + if (STREQ (prlogex, "primary") || + STREQ (prlogex, "logical") || + STREQ (prlogex, "extended")) ; - else if (strcmp (prlogex, "p") == 0) + else if (STREQ (prlogex, "p")) prlogex = "primary"; - else if (strcmp (prlogex, "l") == 0) + else if (STREQ (prlogex, "l")) prlogex = "logical"; - else if (strcmp (prlogex, "e") == 0) + else if (STREQ (prlogex, "e")) prlogex = "extended"; else { - reply_with_error ("part-add: unknown partition type: %s: this should be \"primary\", \"logical\" or \"extended\"", prlogex); + reply_with_error ("unknown partition type: %s: this should be \"primary\", \"logical\" or \"extended\"", prlogex); return -1; } if (startsect < 0) { - reply_with_error ("part-add: startsect cannot be negative"); + reply_with_error ("startsect cannot be negative"); return -1; } /* but endsect can be negative */ @@ -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,35 +159,47 @@ do_part_add (const char *device, const char *prlogex, } int -do_part_disk (const char *device, const char *parttype) +do_part_del (const char *device, int partnum) { - const char *startstr; - const char *endstr; + 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) +{ parttype = check_parttype (parttype); if (!parttype) { - reply_with_error ("part-disk: unknown partition type: common choices are \"gpt\" and \"msdos\""); + reply_with_error ("unknown partition type: common choices are \"gpt\" and \"msdos\""); return -1; } - /* Voooooodooooooooo (thanks Jim Meyering for working this out). */ - if (strcmp (parttype, "msdos") == 0) { - startstr = "1s"; - endstr = "-1s"; - } else if (strcmp (parttype, "gpt") == 0) { - startstr = "34s"; - endstr = "-34s"; - } else { - /* untested */ - startstr = "1s"; - endstr = "-1s"; - } + /* Align all partitions created this way to 64 sectors, and leave + * the last 64 sectors at the end of the disk free. This wastes + * 32K+32K = 64K on 512-byte sector disks. The rationale is: + * + * - aligned operations are faster + * - GPT requires at least 34 sectors at the end of the disk. + */ + const char *startstr = "64s"; + const char *endstr = "-64s"; - RUN_PARTED (device, - "mklabel", parttype, - /* See comment about about the parted mkpart command. */ - "mkpart", strcmp (parttype, "gpt") == 0 ? "p1" : "primary", - startstr, endstr, NULL); + RUN_PARTED (return -1, + device, + "mklabel", parttype, + /* See comment about about the parted mkpart command. */ + "mkpart", STREQ (parttype, "gpt") ? "p1" : "primary", + startstr, endstr, NULL); udev_settle (); @@ -197,11 +209,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 +229,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, "/sbin/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 +328,213 @@ print_partition_table (const char *device) } free (err); - lines = split_lines (out); - free (out); - - if (!lines) - return NULL; - - if (lines[0] == NULL || strcmp (lines[0], "BYT;") != 0) { - reply_with_error ("parted print: 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 print: 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 ("part_get_parttype: 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; + + /* If "loop" return an error (RHBZ#634246). */ + if (STREQ (r, "loop")) { + free (r); + reply_with_error ("not a partitioned device"); + 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; + } + + /* If "loop" return an error (RHBZ#634246). */ + if (STREQ (p, "loop")) { + free (p); + reply_with_error ("not a partitioned device"); + 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; + } - /* 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 ("part_list: could not parse row from output of parted print command: %s", lines[row]); - goto error3; + if (end == 0) { + reply_with_error ("parted output has no blank after end of table"); + goto error1; + } + + size_t nr_rows = end - start; + + 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 +549,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; +}