]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - disk-utils/sfdisk.c
sfdisk: (move-data) improve MiB/s progress bar
[thirdparty/util-linux.git] / disk-utils / sfdisk.c
index 10307ad14f200b94ca78b1c9e66b49d2ecb9c164..3bda79cecaf2a8e865d497307f82e4a75993982c 100644 (file)
 #include <fcntl.h>
 #include <libsmartcols.h>
 #ifdef HAVE_LIBREADLINE
+# define _FUNCTION_DEF
 # include <readline/readline.h>
 #endif
 #include <libgen.h>
+#include <sys/time.h>
 
 #include "c.h"
 #include "xalloc.h"
@@ -47,7 +49,8 @@
 #include "blkdev.h"
 #include "all-io.h"
 #include "rpmatch.h"
-#include "loopdev.h"
+#include "optutils.h"
+#include "ttyutils.h"
 
 #include "libfdisk.h"
 #include "fdisk-list.h"
@@ -55,7 +58,7 @@
 /*
  * sfdisk debug stuff (see fdisk.h and include/debug.h)
  */
-UL_DEBUG_DEFINE_MASK(sfdisk);
+static UL_DEBUG_DEFINE_MASK(sfdisk);
 UL_DEBUG_DEFINE_MASKNAMES(sfdisk) = UL_DEBUG_EMPTY_MASKNAMES;
 
 #define SFDISKPROG_DEBUG_INIT  (1 << 1)
@@ -110,6 +113,7 @@ struct sfdisk {
                     append : 1,        /* don't create new PT, append partitions only */
                     json : 1,          /* JSON dump */
                     movedata: 1,       /* move data after resize */
+                    movefsync: 1, /* use fsync() afetr each write() */
                     notell : 1,        /* don't tell kernel aout new PT */
                     noact  : 1;        /* do not write to device */
 };
@@ -118,7 +122,7 @@ struct sfdisk {
 
 static void sfdiskprog_init_debug(void)
 {
-       __UL_INIT_DEBUG(sfdisk, SFDISKPROG_DEBUG_, 0, SFDISK_DEBUG);
+       __UL_INIT_DEBUG_FROM_ENV(sfdisk, SFDISKPROG_DEBUG_, 0, SFDISK_DEBUG);
 }
 
 
@@ -132,7 +136,7 @@ static int get_user_reply(const char *prompt, char *buf, size_t bufsz)
                p = readline(prompt);
                if (!p)
                        return 1;
-               memcpy(buf, p, bufsz);
+               xstrncpy(buf, p, bufsz);
                free(p);
        } else
 #endif
@@ -189,7 +193,7 @@ static int ask_callback(struct fdisk_context *cxt __attribute__((__unused__)),
                break;
        case FDISK_ASKTYPE_YESNO:
        {
-               char buf[BUFSIZ];
+               char buf[BUFSIZ] = { '\0' };
                fputc('\n', stdout);
                do {
                        int x;
@@ -322,7 +326,7 @@ static char *mk_backup_filename_tpl(const char *filename, const char *devname, c
 
        name = basename(buf);
 
-       if (!filename) {
+       if (!filename || strcmp(filename, "@default") == 0) {
                const char *home = getenv ("HOME");
                if (!home)
                        errx(EXIT_FAILURE, _("failed to create a backup file, $HOME undefined"));
@@ -368,10 +372,12 @@ static int move_partition_data(struct sfdisk *sf, size_t partno, struct fdisk_pa
        char *devname = NULL, *typescript = NULL, *buf = NULL;
        FILE *f = NULL;
        int ok = 0, fd, backward = 0;
-       fdisk_sector_t nsectors, from, to, step, i;
-       size_t ss, step_bytes, cc;
+       fdisk_sector_t nsectors, from, to, step, i, prev;
+       size_t io, ss, step_bytes, cc;
        uintmax_t src, dst;
-       int errsv;
+       int errsv, progress = 0;
+       struct timeval prev_time;
+       uint64_t bytes_per_sec = 0;
 
        assert(sf->movedata);
 
@@ -398,7 +404,7 @@ static int move_partition_data(struct sfdisk *sf, size_t partno, struct fdisk_pa
 
        fd = fdisk_get_devfd(sf->cxt);
 
-       ss = fdisk_get_sector_size(sf->cxt);
+       /* set move direction and overlay */
        nsectors = fdisk_partition_get_size(orig_pa);
        from = fdisk_partition_get_start(orig_pa);
        to = fdisk_partition_get_start(pa);
@@ -410,22 +416,24 @@ static int move_partition_data(struct sfdisk *sf, size_t partno, struct fdisk_pa
                DBG(MISC, ul_debug("overlay between source and target"));
                backward = from < to;
                DBG(MISC, ul_debug(" copy order: %s", backward ? "backward" : "forward"));
+       }
 
-               step = from > to ? from - to : to - from;
-               if (step > nsectors)
-                       step = nsectors;
-       } else
-               step = nsectors;
+       /* set optimal step size -- nearest to 1MiB aligned to optimal I/O */
+       io = fdisk_get_optimal_iosize(sf->cxt);
+       ss = fdisk_get_sector_size(sf->cxt);
+       if (!io)
+               io = ss;
+       if (io < 1024 * 1024)
+               step_bytes = ((1024 * 1024) + io/2) / io * io;
+       else
+               step_bytes = io;
 
-       /* make step usable for malloc() */
-       if (step * ss > (getpagesize() * 256U))
-               step = (getpagesize() * 256) / ss;
+       step = step_bytes / ss;
 
        /* align the step (note that nsectors does not have to be power of 2) */
        while (nsectors % step)
                step--;
 
-       step_bytes = step * ss;
        DBG(MISC, ul_debug(" step: %ju (%zu bytes)", (uintmax_t)step, step_bytes));
 
 #if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE)
@@ -433,17 +441,24 @@ static int move_partition_data(struct sfdisk *sf, size_t partno, struct fdisk_pa
                posix_fadvise(fd, from * ss, nsectors * ss, POSIX_FADV_SEQUENTIAL);
 #endif
        devname = fdisk_partname(fdisk_get_devname(sf->cxt), partno+1);
-       typescript = mk_backup_filename_tpl(sf->move_typescript, devname, ".move");
+       if (sf->move_typescript)
+               typescript = mk_backup_filename_tpl(sf->move_typescript, devname, ".move");
 
        if (!sf->quiet) {
                fdisk_info(sf->cxt,"");
                color_scheme_enable("header", UL_COLOR_BOLD);
-               fdisk_info(sf->cxt, _("Data move:"));
+               fdisk_info(sf->cxt, sf->noact ? _("Data move: (--no-act)") : _("Data move:"));
                color_disable();
-               fdisk_info(sf->cxt, _(" typescript file: %s"), typescript);
-               printf(_(" old start: %ju, new start: %ju (move %ju sectors)\n"),
-                       (uintmax_t) from, (uintmax_t) to, (uintmax_t) nsectors);
+               if (typescript)
+                       fdisk_info(sf->cxt, _(" typescript file: %s"), typescript);
+               printf(_("  start sector: (from/to) %ju / %ju\n"), (uintmax_t) from, (uintmax_t) to);
+               printf(_("  sectors: %ju\n"), (uintmax_t) nsectors);
+               printf(_("  step size: %zu bytes\n"), step_bytes);
+               putchar('\n');
                fflush(stdout);
+
+               if (isatty(fileno(stdout)))
+                       progress = 1;
        }
 
        if (sf->interactive) {
@@ -455,26 +470,30 @@ static int move_partition_data(struct sfdisk *sf, size_t partno, struct fdisk_pa
                }
        }
 
-       f = fopen(typescript, "w");
-       if (!f)
-               goto fail;
+       if (typescript) {
+               f = fopen(typescript, "w");
+               if (!f) {
+                       fdisk_warn(sf->cxt, _("cannot open %s"), typescript);
+                       goto fail;
+               }
 
-       /* don't translate */
-       fprintf(f, "# sfdisk: " PACKAGE_STRING "\n");
-       fprintf(f, "# Disk: %s\n", devname);
-       fprintf(f, "# Partition: %zu\n", partno + 1);
-       fprintf(f, "# Operation: move data\n");
-       fprintf(f, "# Original start offset (sectors/bytes): %ju/%ju\n",
-               (uintmax_t)from, (uintmax_t)from * ss);
-       fprintf(f, "# New start offset (sectors/bytes): %ju/%ju\n",
-               (uintmax_t)to, (uintmax_t)to * ss);
-       fprintf(f, "# Area size (sectors/bytes): %ju/%ju\n",
-               (uintmax_t)nsectors, (uintmax_t)nsectors * ss);
-       fprintf(f, "# Sector size: %zu\n", ss);
-       fprintf(f, "# Step size (in bytes): %zu\n", step_bytes);
-       fprintf(f, "# Steps: %ju\n", (uintmax_t)(nsectors / step));
-       fprintf(f, "#\n");
-       fprintf(f, "# <step>: <from> <to> (step offsets in bytes)\n");
+               /* don't translate */
+               fprintf(f, "# sfdisk: " PACKAGE_STRING "\n");
+               fprintf(f, "# Disk: %s\n", devname);
+               fprintf(f, "# Partition: %zu\n", partno + 1);
+               fprintf(f, "# Operation: move data\n");
+               fprintf(f, "# Original start offset (sectors/bytes): %ju/%ju\n",
+                       (uintmax_t)from, (uintmax_t)from * ss);
+               fprintf(f, "# New start offset (sectors/bytes): %ju/%ju\n",
+                       (uintmax_t)to, (uintmax_t)to * ss);
+               fprintf(f, "# Area size (sectors/bytes): %ju/%ju\n",
+                       (uintmax_t)nsectors, (uintmax_t)nsectors * ss);
+               fprintf(f, "# Sector size: %zu\n", ss);
+               fprintf(f, "# Step size (in bytes): %zu\n", step_bytes);
+               fprintf(f, "# Steps: %ju\n", (uintmax_t)(nsectors / step));
+               fprintf(f, "#\n");
+               fprintf(f, "# <step>: <from> <to> (step offsets in bytes)\n");
+       }
 
        src = (backward ? from + nsectors : from) * ss;
        dst = (backward ? to + nsectors : to) * ss;
@@ -482,6 +501,9 @@ static int move_partition_data(struct sfdisk *sf, size_t partno, struct fdisk_pa
 
        DBG(MISC, ul_debug(" initial: src=%ju dst=%ju", src, dst));
 
+       gettimeofday(&prev_time, NULL);
+       prev = 0;
+
        for (cc = 1, i = 0; i < nsectors; i += step, cc++) {
                ssize_t rc;
 
@@ -490,36 +512,82 @@ static int move_partition_data(struct sfdisk *sf, size_t partno, struct fdisk_pa
 
                DBG(MISC, ul_debug("#%05zu: src=%ju dst=%ju", cc, src, dst));
 
-               /* read source */
-               if (lseek(fd, src, SEEK_SET) == (off_t) -1)
-                       goto fail;
-               rc = read(fd, buf, step_bytes);
-               if (rc < 0 || rc != (ssize_t) step_bytes)
-                       goto fail;
-
-               /* write target */
-               if (lseek(fd, dst, SEEK_SET) == (off_t) -1)
-                       goto fail;
-               rc = write(fd, buf, step_bytes);
-               if (rc < 0 || rc != (ssize_t) step_bytes)
-                       goto fail;
-               fsync(fd);
+               if (!sf->noact) {
+                       /* read source */
+                       if (lseek(fd, src, SEEK_SET) == (off_t) -1)
+                               goto fail;
+                       rc = read(fd, buf, step_bytes);
+                       if (rc < 0 || rc != (ssize_t) step_bytes)
+                               goto fail;
+
+                       /* write target */
+                       if (lseek(fd, dst, SEEK_SET) == (off_t) -1)
+                               goto fail;
+                       rc = write(fd, buf, step_bytes);
+                       if (rc < 0 || rc != (ssize_t) step_bytes)
+                               goto fail;
+                       if (sf->movefsync)
+                               fsync(fd);
+               }
 
                /* write log */
-               fprintf(f, "%05zu: %12ju %12ju\n", cc, src, dst);
+               if (f)
+                       fprintf(f, "%05zu: %12ju %12ju\n", cc, src, dst);
+
+               if (progress && i % 10 == 0) {
+                       unsigned int elapsed = 0;       /* usec */
+                       struct timeval cur_time;
+
+                       gettimeofday(&cur_time, NULL);
+                       if (cur_time.tv_sec - prev_time.tv_sec > 1) {
+                               elapsed = ((cur_time.tv_sec - prev_time.tv_sec) * 1000000) +
+                                         (cur_time.tv_usec - prev_time.tv_usec);
+
+                               bytes_per_sec = ((i - prev) * ss) / elapsed;    /* per usec */
+                               bytes_per_sec *= 1000000;                       /* per sec */
+
+                               prev_time = cur_time;
+                               prev = i;
+                       }
+
+                       if (bytes_per_sec)
+                               fprintf(stdout, _("Moved %ju from %ju sectors (%.3f%%, %.1f MiB/s)."),
+                                       i + 1, nsectors,
+                                       100.0 / ((double) nsectors/(i+1)),
+                                       (double) (bytes_per_sec / (1024 * 1024)));
+                       else
+                               fprintf(stdout, _("Moved %ju from %ju sectors (%.3f%%)."),
+                                       i + 1, nsectors,
+                                       100.0 / ((double) nsectors/(i+1)));
+                       fflush(stdout);
+                        fputc('\r', stdout);
+
+               }
 
-#if defined(POSIX_FADV_DONTNEED) && defined(HAVE_POSIX_FADVISE)
-               posix_fadvise(fd, src, step_bytes, POSIX_FADV_DONTNEED);
-#endif
                if (!backward)
                        src += step_bytes, dst += step_bytes;
        }
 
-       fclose(f);
+       if (progress) {
+               int x = get_terminal_width(80);
+               for (; x > 0; x--)
+                       fputc(' ', stdout);
+               fflush(stdout);
+               fputc('\r', stdout);
+               fprintf(stdout, _("Moved %ju from %ju sectors (%.3f%%)."),
+                               i, nsectors,
+                               100.0 / ((double) nsectors/(i+1)));
+               fputc('\n', stdout);
+       }
+       if (f)
+               fclose(f);
        free(buf);
        free(devname);
        free(typescript);
 
+       if (sf->noact)
+               fdisk_info(sf->cxt, _("Your data has not been moved (--no-act)."));
+
        return 0;
 fail:
        errsv = -errno;
@@ -539,16 +607,25 @@ static int write_changes(struct sfdisk *sf)
 
        if (sf->noact)
                fdisk_info(sf->cxt, _("The partition table is unchanged (--no-act)."));
-       else {
+       else
                rc = fdisk_write_disklabel(sf->cxt);
-               if (rc == 0 && sf->movedata && sf->orig_pa)
-                       rc = move_partition_data(sf, sf->partno, sf->orig_pa);
-               if (!rc) {
-                       fdisk_info(sf->cxt, _("\nThe partition table has been altered."));
-                       if (!sf->notell)
-                               fdisk_reread_partition_table(sf->cxt);
+
+       if (rc == 0 && sf->movedata && sf->orig_pa)
+               rc = move_partition_data(sf, sf->partno, sf->orig_pa);
+
+       if (!sf->noact && !rc) {
+               fdisk_info(sf->cxt, _("\nThe partition table has been altered."));
+               if (!sf->notell) {
+                       /* Let's wait a little bit. It's possible that our
+                        * system is still busy with a previous re-read
+                        * ioctl (on sfdisk start) or with another task
+                        * related to the write to the device.
+                        */
+                       xusleep(250000);
+                       fdisk_reread_partition_table(sf->cxt);
                }
        }
+
        if (!rc)
                rc = fdisk_deassign_device(sf->cxt,
                                sf->noact || sf->notell);       /* no-sync */
@@ -815,8 +892,18 @@ static int command_activate(struct sfdisk *sf, int argc, char **argv)
        if (rc)
                err(EXIT_FAILURE, _("cannot open %s"), devname);
 
-       if (!fdisk_is_label(sf->cxt, DOS))
-               errx(EXIT_FAILURE, _("toggle boot flags is supported for MBR only"));
+       if (fdisk_is_label(sf->cxt, GPT)) {
+               if (fdisk_gpt_is_hybrid(sf->cxt))
+                       errx(EXIT_FAILURE, _("toggle boot flags is unsupported for Hybrid GPT/MBR"));
+
+               /* Switch from GPT to PMBR */
+               sf->cxt = fdisk_new_nested_context(sf->cxt, "dos");
+               if (!sf->cxt)
+                       err(EXIT_FAILURE, _("cannot switch to PMBR"));
+               fdisk_info(sf->cxt, _("Activation is unsupported for GPT -- entering nested PMBR."));
+
+       } else if (!fdisk_is_label(sf->cxt, DOS))
+               errx(EXIT_FAILURE, _("toggle boot flags is supported for MBR or PMBR only"));
 
        if (!listonly && sf->backup)
                backup_partition_table(sf, devname);
@@ -847,7 +934,11 @@ static int command_activate(struct sfdisk *sf, int argc, char **argv)
 
        /* sfdisk --activate <partno> [..] */
        for (i = 1; i < argc; i++) {
-               int n = strtou32_or_err(argv[i], _("failed to parse partition number"));
+               int n;
+
+               if (i == 1 && strcmp(argv[1], "-") == 0)
+                       break;
+               n = strtou32_or_err(argv[i], _("failed to parse partition number"));
 
                rc = fdisk_toggle_partition_flag(sf->cxt, n - 1, DOS_FLAG_ACTIVE);
                if (rc)
@@ -857,6 +948,7 @@ static int command_activate(struct sfdisk *sf, int argc, char **argv)
        }
 
        fdisk_unref_partition(pa);
+
        if (listonly)
                rc = fdisk_deassign_device(sf->cxt, 1);
        else
@@ -1294,8 +1386,8 @@ static void command_fdisk_help(void)
 
        fputc('\n', stdout);
        fputs(_("   <type>   The partition type.  Default is a Linux data partition.\n"), stdout);
-       fputs(_("            MBR: hex or L,S,E,X shortcuts.\n"), stdout);
-       fputs(_("            GPT: UUID or L,S,H shortcuts.\n"), stdout);
+       fputs(_("            MBR: hex or L,S,E,X,U,R,V shortcuts.\n"), stdout);
+       fputs(_("            GPT: UUID or L,S,H,U,R,V shortcuts.\n"), stdout);
 
        fputc('\n', stdout);
        fputs(_("   <bootable>  Use '*' to mark an MBR partition as bootable.\n"), stdout);
@@ -1390,26 +1482,6 @@ static size_t last_pt_partno(struct sfdisk *sf)
        return partno;
 }
 
-static int is_device_used(struct sfdisk *sf)
-{
-#ifdef BLKRRPART
-       struct stat st;
-       int fd;
-
-       assert(sf);
-       assert(sf->cxt);
-
-       fd = fdisk_get_devfd(sf->cxt);
-       if (fd < 0)
-               return 0;
-
-       if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)
-           && major(st.st_rdev) != LOOPDEV_MAJOR)
-               return ioctl(fd, BLKRRPART) != 0;
-#endif
-       return 0;
-}
-
 #ifdef HAVE_LIBREADLINE
 static char *sfdisk_fgets(struct fdisk_script *dp,
                          char *buf, size_t bufsz, FILE *f)
@@ -1457,10 +1529,42 @@ static int ignore_partition(struct fdisk_partition *pa)
        return 0;
 }
 
+static void follow_wipe_mode(struct sfdisk *sf)
+{
+       int dowipe = sf->wipemode == WIPEMODE_ALWAYS ? 1 : 0;
+
+       if (sf->interactive && sf->wipemode == WIPEMODE_AUTO)
+               dowipe = 1;     /* do it in interactive mode */
+
+       if (fdisk_is_ptcollision(sf->cxt) && sf->wipemode != WIPEMODE_NEVER)
+               dowipe = 1;     /* always wipe old PT */
+
+       fdisk_enable_wipe(sf->cxt, dowipe);
+       if (sf->quiet)
+               return;
+
+       if (dowipe) {
+               if (!fdisk_is_ptcollision(sf->cxt)) {
+                       fdisk_warnx(sf->cxt, _(
+                               "The device contains '%s' signature and it will be removed by a write command. "
+                               "See sfdisk(8) man page and --wipe option for more details."),
+                               fdisk_get_collision(sf->cxt));
+                       fputc('\n', stdout);
+               }
+       } else {
+               fdisk_warnx(sf->cxt, _(
+                       "The device contains '%s' signature and it may remain on the device. "
+                       "It is recommended to wipe the device with wipefs(8) or "
+                       "sfdisk --wipe, in order to avoid possible collisions."),
+                       fdisk_get_collision(sf->cxt));
+               fputc('\n', stderr);
+       }
+}
+
 static int wipe_partition(struct sfdisk *sf, size_t partno)
 {
        int rc, yes = 0;
-       char *fstype = NULL;;
+       char *fstype = NULL;
        struct fdisk_partition *tmp = NULL;
 
        DBG(MISC, ul_debug("checking for signature"));
@@ -1592,7 +1696,7 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
        if (!sf->noact && !sf->noreread) {
                if (!sf->quiet)
                        fputs(_("Checking that no-one is using this disk right now ..."), stdout);
-               if (is_device_used(sf)) {
+               if (fdisk_device_is_used(sf->cxt)) {
                        if (!sf->quiet)
                                fputs(_(" FAILED\n\n"), stdout);
 
@@ -1607,25 +1711,8 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
                        fputs(_(" OK\n\n"), stdout);
        }
 
-       if (fdisk_get_collision(sf->cxt)) {
-               int dowipe = sf->wipemode == WIPEMODE_ALWAYS ? 1 : 0;
-
-               fdisk_warnx(sf->cxt, _("Device %s already contains a %s signature."),
-                       devname, fdisk_get_collision(sf->cxt));
-
-               if (sf->interactive && sf->wipemode == WIPEMODE_AUTO)
-                       dowipe = 1;     /* do it in interactive mode */
-
-               fdisk_enable_wipe(sf->cxt, dowipe);
-               if (dowipe)
-                       fdisk_warnx(sf->cxt, _(
-                               "The signature will be removed by a write command."));
-               else
-                       fdisk_warnx(sf->cxt, _(
-                               "It is strongly recommended to wipe the device with "
-                               "wipefs(8), in order to avoid possible collisions."));
-               fputc('\n', stderr);
-       }
+       if (fdisk_get_collision(sf->cxt))
+               follow_wipe_mode(sf);
 
        if (sf->backup)
                backup_partition_table(sf, devname);
@@ -1678,6 +1765,8 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
                }
 
                refresh_prompt_buffer(sf, devname, next_partno, created);
+
+
                if (sf->prompt && (sf->interactive || !sf->quiet)) {
 #ifndef HAVE_LIBREADLINE
                        fputs(sf->prompt, stdout);
@@ -1697,13 +1786,14 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
                        continue;
                } else if (rc == 1) {
                        rc = SFDISK_DONE_EOF;
-                       fputs(_("Done.\n"), stdout);
+                       if (!sf->quiet)
+                               fputs(_("Done.\n"), stdout);
                        break;
                }
 
                nparts = fdisk_table_get_nents(tb);
                if (nparts) {
-                       size_t cur_partno;
+                       size_t cur_partno = (size_t) -1;
                        struct fdisk_partition *pa = fdisk_table_get_partition(tb, nparts - 1);
 
                        assert(pa);
@@ -1720,13 +1810,16 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
                                        fdisk_warnx(sf->cxt, _(
                                          "Failed to apply script headers, "
                                          "disk label not created."));
+
+                               if (rc == 0 && fdisk_get_collision(sf->cxt))
+                                       follow_wipe_mode(sf);
                        }
                        if (!rc && partno >= 0) {       /* -N <partno>, modify partition */
                                rc = fdisk_set_partition(sf->cxt, partno, pa);
                                rc = rc == 0 ? SFDISK_DONE_ASK : SFDISK_DONE_ABORT;
                                break;
                        } else if (!rc) {               /* add partition */
-                               if (!sf->interactive &&
+                               if (!sf->interactive && !sf->quiet &&
                                    (!sf->prompt || startswith(sf->prompt, SFDISK_PROMPT))) {
                                        refresh_prompt_buffer(sf, devname, next_partno, created);
                                        fputs(sf->prompt, stdout);
@@ -1734,8 +1827,7 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
                                rc = fdisk_add_partition(sf->cxt, pa, &cur_partno);
                                if (rc) {
                                        errno = -rc;
-                                       fdisk_warn(sf->cxt, _("Failed to add partition"));
-
+                                       fdisk_warn(sf->cxt, _("Failed to add #%d partition"), next_partno + 1);
                                }
                        }
 
@@ -1766,8 +1858,25 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
                }
        } while (1);
 
+       /* create empty disk label if label, but no partition specified */
+       if ((rc == SFDISK_DONE_EOF || rc == SFDISK_DONE_WRITE) && created == 0
+           && fdisk_script_has_force_label(dp) == 1
+           && fdisk_table_get_nents(tb) == 0
+           && fdisk_script_get_header(dp, "label")) {
+
+               int xrc = fdisk_apply_script_headers(sf->cxt, dp);
+               created = !xrc;
+               if (xrc) {
+                       fdisk_warnx(sf->cxt, _(
+                                 "Failed to apply script headers, "
+                                 "disk label not created."));
+                       rc = SFDISK_DONE_ABORT;
+               }
+       }
+
        if (!sf->quiet && rc != SFDISK_DONE_ABORT) {
                fdisk_info(sf->cxt, _("\nNew situation:"));
+               list_disk_identifier(sf->cxt);
                list_disklabel(sf->cxt);
        }
 
@@ -1783,6 +1892,7 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
                                break;
                        }
                }
+               /* fallthrough */
        case SFDISK_DONE_WRITE:
                rc = write_changes(sf);
                break;
@@ -1796,8 +1906,9 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
        return rc;
 }
 
-static void __attribute__ ((__noreturn__)) usage(FILE *out)
+static void __attribute__((__noreturn__)) usage(void)
 {
+       FILE *out = stdout;
        fputs(USAGE_HEADER, out);
 
        fprintf(out,
@@ -1807,8 +1918,8 @@ static void __attribute__ ((__noreturn__)) usage(FILE *out)
        fputs(USAGE_SEPARATOR, out);
        fputs(_("Display or manipulate a disk partition table.\n"), out);
 
-       fputs(_("\nCommands:\n"), out);
-       fputs(_(" -A, --activate <dev> [<part> ...] list or set bootable MBR partitions\n"), out);
+       fputs(USAGE_COMMANDS, out);
+       fputs(_(" -A, --activate <dev> [<part> ...] list or set bootable (P)MBR partitions\n"), out);
        fputs(_(" -d, --dump <dev>                  dump partition table (usable for later input)\n"), out);
        fputs(_(" -J, --json <dev>                  dump partition table in JSON format\n"), out);
        fputs(_(" -g, --show-geometry [<dev> ...]   list geometry of all or specified devices\n"), out);
@@ -1836,8 +1947,10 @@ static void __attribute__ ((__noreturn__)) usage(FILE *out)
        fputs(_(" -b, --backup              backup partition table sectors (see -O)\n"), out);
        fputs(_("     --bytes               print SIZE in bytes rather than in human readable format\n"), out);
        fputs(_("     --move-data[=<typescript>] move partition data after relocation (requires -N)\n"), out);
+       fputs(_("     --move-use-fsync      use fsync after each write when move data\n"), out);
        fputs(_(" -f, --force               disable all consistency checking\n"), out);
-       fputs(_("     --color[=<when>]      colorize output (auto, always or never)\n"), out);
+       fprintf(out,
+             _("     --color[=<when>]      colorize output (%s, %s or %s)\n"), "auto", "always", "never");
        fprintf(out,
                "                             %s\n", USAGE_COLORS_DEFAULT);
        fputs(_(" -N, --partno <num>        specify partition number\n"), out);
@@ -1847,8 +1960,10 @@ static void __attribute__ ((__noreturn__)) usage(FILE *out)
        fputs(_(" -O, --backup-file <path>  override default backup file name\n"), out);
        fputs(_(" -o, --output <list>       output columns\n"), out);
        fputs(_(" -q, --quiet               suppress extra info messages\n"), out);
-       fputs(_(" -w, --wipe <mode>         wipe signatures (auto, always or never)\n"), out);
-       fputs(_(" -W, --wipe-partitions <mode>  wipe signatures from new partitions (auto, always or never)\n"), out);
+       fprintf(out,
+             _(" -w, --wipe <mode>         wipe signatures (%s, %s or %s)\n"), "auto", "always", "never");
+       fprintf(out,
+             _(" -W, --wipe-partitions <mode>  wipe signatures from new partitions (%s, %s or %s)\n"), "auto", "always", "never");
        fputs(_(" -X, --label <name>        specify label type (dos, gpt, ...)\n"), out);
        fputs(_(" -Y, --label-nested <name> specify nested label type (dos, bsd)\n"), out);
        fputs(USAGE_SEPARATOR, out);
@@ -1857,13 +1972,13 @@ static void __attribute__ ((__noreturn__)) usage(FILE *out)
        fputs(_(" -u, --unit S              deprecated, only sector unit is supported\n"), out);
 
        fputs(USAGE_SEPARATOR, out);
-       fputs(USAGE_HELP, out);
-       fputs(_(" -v, --version  output version information and exit\n"), out);
+       printf( " -h, --help                %s\n", USAGE_OPTSTR_HELP);
+       printf( " -v, --version             %s\n", USAGE_OPTSTR_VERSION);
 
        list_available_columns(out);
 
-       fprintf(out, USAGE_MAN_TAIL("sfdisk(8)"));
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       printf(USAGE_MAN_TAIL("sfdisk(8)"));
+       exit(EXIT_SUCCESS);
 }
 
 
@@ -1891,6 +2006,7 @@ int main(int argc, char *argv[])
                OPT_BYTES,
                OPT_COLOR,
                OPT_MOVEDATA,
+               OPT_MOVEFSYNC,
                OPT_DELETE,
                OPT_NOTELL
        };
@@ -1916,10 +2032,10 @@ int main(int argc, char *argv[])
                { "no-reread", no_argument,     NULL, OPT_NOREREAD },
                { "no-tell-kernel", no_argument, NULL, OPT_NOTELL },
                { "move-data", optional_argument, NULL, OPT_MOVEDATA },
+               { "move-use-fsync", no_argument, NULL, OPT_MOVEFSYNC },
                { "output",  required_argument, NULL, 'o' },
                { "partno",  required_argument, NULL, 'N' },
                { "reorder", no_argument,       NULL, 'r' },
-               { "show-size", no_argument,     NULL, 's' },
                { "show-geometry", no_argument, NULL, 'g' },
                { "quiet",   no_argument,       NULL, 'q' },
                { "verify",  no_argument,       NULL, 'V' },
@@ -1933,23 +2049,34 @@ int main(int argc, char *argv[])
                { "part-attrs", no_argument,    NULL, OPT_PARTATTRS },
 
                { "show-pt-geometry", no_argument, NULL, 'G' },         /* deprecated */
-               { "unit",    required_argument, NULL, 'u' },
+               { "unit",    required_argument, NULL, 'u' },            /* deprecated */
                { "Linux",   no_argument,       NULL, 'L' },            /* deprecated */
+               { "show-size", no_argument,     NULL, 's' },            /* deprecated */
 
                { "change-id",no_argument,      NULL, OPT_CHANGE_ID },  /* deprecated */
                { "id",      no_argument,       NULL, 'c' },            /* deprecated */
                { "print-id",no_argument,       NULL, OPT_PRINT_ID },   /* deprecated */
 
-               { NULL, 0, 0, 0 },
+               { NULL, 0, NULL, 0 },
        };
+       static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
+               { 'F','J','d'},                 /* --list-free --json --dump */
+               { 's','u'},                     /* --show-size --unit */
+               { 0 }
+       };
+       int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
 
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout);
+       close_stdout_atexit();
 
        while ((c = getopt_long(argc, argv, "aAbcdfFgGhJlLo:O:nN:qrsTu:vVX:Y:w:W:",
                                        longopts, &longidx)) != -1) {
+
+               err_exclusive_options(c, longopts, excl, excl_st);
+
                switch(c) {
                case 'A':
                        sf->act = ACT_ACTIVATE;
@@ -1985,11 +2112,12 @@ int main(int argc, char *argv[])
                        break;
                case 'G':
                        warnx(_("--show-pt-geometry is no more implemented. Using --show-geometry."));
+                       /* fallthrough */
                case 'g':
                        sf->act = ACT_SHOW_GEOM;
                        break;
                case 'h':
-                       usage(stdout);
+                       usage();
                        break;
                case 'l':
                        sf->act = ACT_LIST;
@@ -2027,9 +2155,7 @@ int main(int argc, char *argv[])
                                errx(EXIT_FAILURE, _("unsupported unit '%c'"), *optarg);
                        break;
                case 'v':
-                       printf(_("%s from %s\n"), program_invocation_short_name,
-                                                 PACKAGE_STRING);
-                       return EXIT_SUCCESS;
+                       print_version(EXIT_SUCCESS);
                case 'V':
                        sf->verify = 1;
                        break;
@@ -2078,6 +2204,9 @@ int main(int argc, char *argv[])
                        sf->movedata = 1;
                        sf->move_typescript = optarg;
                        break;
+               case OPT_MOVEFSYNC:
+                       sf->movefsync = 1;
+                       break;
                case OPT_DELETE:
                        sf->act = ACT_DELETE;
                        break;
@@ -2085,7 +2214,7 @@ int main(int argc, char *argv[])
                        sf->notell = 1;
                        break;
                default:
-                       usage(stderr);
+                       errtryhelp(EXIT_FAILURE);
                }
        }