Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package parti for openSUSE:Factory checked 
in at 2025-05-12 16:47:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/parti (Old)
 and      /work/SRC/openSUSE:Factory/.parti.new.30101 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "parti"

Mon May 12 16:47:05 2025 rev:18 rq:1276303 version:2.8

Changes:
--------
--- /work/SRC/openSUSE:Factory/parti/parti.changes      2025-05-08 
18:25:01.960610705 +0200
+++ /work/SRC/openSUSE:Factory/.parti.new.30101/parti.changes   2025-05-12 
16:50:10.339923674 +0200
@@ -1,0 +2,11 @@
+Fri May 9 15:41:25 UTC 2025 - wfe...@opensuse.org
+
+- merge gh#wfeldt/parti#21
+- improved unify-gpt
+- parti: add disk size in blocks
+- add unify-gpt man page
+- unify-gpt: fixed layout calculation, better logging
+- adjust documentation
+- 2.8
+
+--------------------------------------------------------------------

Old:
----
  parti-2.7.tar.xz

New:
----
  parti-2.8.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ parti.spec ++++++
--- /var/tmp/diff_new_pack.7t0RVM/_old  2025-05-12 16:50:10.827944142 +0200
+++ /var/tmp/diff_new_pack.7t0RVM/_new  2025-05-12 16:50:10.827944142 +0200
@@ -18,7 +18,7 @@
 
 
 Name:           parti
-Version:        2.7
+Version:        2.8
 Release:        0
 Summary:        Show partition table information
 License:        GPL-3.0-only
@@ -28,6 +28,7 @@
 BuildRequires:  libmediacheck-devel
 BuildRequires:  pkgconfig
 BuildRequires:  xz
+BuildRequires:  rubygem(asciidoctor)
 BuildRequires:  pkgconfig(blkid)
 BuildRequires:  pkgconfig(json-c)
 BuildRequires:  pkgconfig(uuid)
@@ -63,4 +64,5 @@
 %{_bindir}/unify-gpt
 %license COPYING
 %doc README.md
+%doc %{_mandir}/man1/unify-gpt.*
 

++++++ parti-2.7.tar.xz -> parti-2.8.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/Makefile new/parti-2.8/Makefile
--- old/parti-2.7/Makefile      2025-05-07 20:07:30.000000000 +0200
+++ new/parti-2.8/Makefile      2025-05-09 17:41:25.000000000 +0200
@@ -3,6 +3,7 @@
 XFLAGS  = -Wno-pointer-sign -Wsign-conversion -Wsign-compare
 LDFLAGS = -ljson-c -luuid -lblkid -lmediacheck
 BINDIR  = /usr/bin
+MANDIR  = /usr/share/man
 
 GIT2LOG := $(shell if [ -x ./git2log ] ; then echo ./git2log --update ; else 
echo true ; fi)
 GITDEPS := $(shell [ -d .git ] && echo .git/HEAD .git/refs/heads 
.git/refs/tags)
@@ -32,9 +33,13 @@
 unify-gpt: unify-gpt.c
        $(CC) $(CFLAGS) $(XFLAGS) $^ -o $@
 
-install: parti
+install: parti unify-gpt doc
        install -m 755 -D parti $(DESTDIR)$(BINDIR)/parti
        install -m 755 -D unify-gpt $(DESTDIR)$(BINDIR)/unify-gpt
+       install -D -m 644 unify-gpt.1 $(DESTDIR)$(MANDIR)/man1/unify-gpt.1
+
+doc:
+       asciidoctor -b manpage -a version=$(VERSION) unify-gpt_man.adoc
 
 archive: changelog
        mkdir -p package
@@ -43,5 +48,5 @@
        xz -f package/$(PREFIX).tar
 
 clean:
-       rm -f *~ *.o parti unify-gpt changelog VERSION
+       rm -f *~ *.o parti unify-gpt unify-gpt.1 changelog VERSION
        rm -rf package
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/VERSION new/parti-2.8/VERSION
--- old/parti-2.7/VERSION       2025-05-07 20:07:30.000000000 +0200
+++ new/parti-2.8/VERSION       2025-05-09 17:41:25.000000000 +0200
@@ -1 +1 @@
-2.7
+2.8
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/changelog new/parti-2.8/changelog
--- old/parti-2.7/changelog     2025-05-07 20:07:30.000000000 +0200
+++ new/parti-2.8/changelog     2025-05-09 17:41:25.000000000 +0200
@@ -1,3 +1,11 @@
+2025-05-09:    2.8
+       - merge gh#wfeldt/parti#21
+       - improved unify-gpt
+       - parti: add disk size in blocks
+       - add unify-gpt man page
+       - unify-gpt: fixed layout calculation, better logging
+       - adjust documentation
+
 2025-05-07:    2.7
        - merge gh#wfeldt/parti#19
        - parti.spec: bring spec file to 2024
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/obs/parti.spec new/parti-2.8/obs/parti.spec
--- old/parti-2.7/obs/parti.spec        2025-05-07 20:07:30.000000000 +0200
+++ new/parti-2.8/obs/parti.spec        2025-05-09 17:41:25.000000000 +0200
@@ -28,6 +28,7 @@
 BuildRequires:  libmediacheck-devel
 BuildRequires:  pkgconfig
 BuildRequires:  xz
+BuildRequires:  rubygem(asciidoctor)
 BuildRequires:  pkgconfig(blkid)
 BuildRequires:  pkgconfig(json-c)
 BuildRequires:  pkgconfig(uuid)
@@ -63,5 +64,6 @@
 %{_bindir}/unify-gpt
 %license COPYING
 %doc README.md
+%doc %{_mandir}/man1/unify-gpt.*
 
 %changelog
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/ptable_apple.c new/parti-2.8/ptable_apple.c
--- old/parti-2.7/ptable_apple.c        2025-05-07 20:07:30.000000000 +0200
+++ new/parti-2.8/ptable_apple.c        2025-05-09 17:41:25.000000000 +0200
@@ -41,10 +41,12 @@
   json_object_object_add(disk->json_disk, "apple", json_apple);
 
   json_object_object_add(json_apple, "block_size", 
json_object_new_int(disk->block_size));
+  json_object_object_add(json_apple, "disk_size", 
json_object_new_int(disk->size_in_bytes / disk->block_size));
   json_object_object_add(json_apple, "entries", json_object_new_int(parts));
 
   log_info(SEP "\napple partition table: %d entries\n", parts);
   log_info("  sector size: %d\n", disk->block_size);
+  log_info("  disk size: %"PRIu64"\n", disk->size_in_bytes / disk->block_size);
 
   json_object *json_table = json_object_new_array();
   json_object_object_add(json_apple, "partitions", json_table);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/ptable_gpt.c new/parti-2.8/ptable_gpt.c
--- old/parti-2.7/ptable_gpt.c  2025-05-07 20:07:30.000000000 +0200
+++ new/parti-2.8/ptable_gpt.c  2025-05-09 17:41:25.000000000 +0200
@@ -222,10 +222,12 @@
 
   json_object_object_add(json_gpt, "revision", json_object_new_format("%u.%u", 
gpt->revision >> 16, gpt->revision & 0xffff));
   json_object_object_add(json_gpt, "block_size", 
json_object_new_int(disk->block_size));
+  json_object_object_add(json_gpt, "disk_size", 
json_object_new_int(disk->size_in_bytes / disk->block_size));
   json_object_object_add(json_gpt, "guid", json_object_new_string(guid));
 
   log_info(SEP "\ngpt (%s) guid: %s\n", addr == 1 ? "primary" : "backup", 
guid);
   log_info("  sector size: %u\n", disk->block_size);
+  log_info("  disk size: %"PRIu64"\n", disk->size_in_bytes / disk->block_size);
   log_info("  revision: %u.%u\n", gpt->revision >> 16, gpt->revision & 0xffff);
 
   json_object *json_header = json_object_new_object();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/ptable_mbr.c new/parti-2.8/ptable_mbr.c
--- old/parti-2.7/ptable_mbr.c  2025-05-07 20:07:30.000000000 +0200
+++ new/parti-2.8/ptable_mbr.c  2025-05-09 17:41:25.000000000 +0200
@@ -297,7 +297,9 @@
   log_info(SEP "\nmbr id: 0x%08x\n", id);
 
   json_object_object_add(json_mbr, "block_size", 
json_object_new_int(disk->block_size));
+  json_object_object_add(json_mbr, "disk_size", 
json_object_new_int(disk->size_in_bytes / disk->block_size));
   log_info("  sector size: %u\n", disk->block_size);
+  log_info("  disk size: %"PRIu64"\n", disk->size_in_bytes / disk->block_size);
 
   json_object_object_add(json_mbr, "id", json_object_new_format("0x%08x", id));
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/unify-gpt.c new/parti-2.8/unify-gpt.c
--- old/parti-2.7/unify-gpt.c   2025-05-07 20:07:30.000000000 +0200
+++ new/parti-2.8/unify-gpt.c   2025-05-09 17:41:25.000000000 +0200
@@ -7,11 +7,9 @@
 #include <getopt.h>
 #include <string.h>
 #include <inttypes.h>
-#include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/ioctl.h>
-#include <sys/syscall.h>
 #include <sys/mount.h>
 #include <uuid/uuid.h>
 
@@ -19,7 +17,6 @@
 #define VERSION "0.0"
 #endif
 
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 #define GPT_SIGNATURE   0x5452415020494645ll
 
 #define MIN_BLOCK_SHIFT        9
@@ -29,14 +26,22 @@
 #define MIN(a, b)      ((a) < (b) ? (a) : (b))
 #define MAX(a, b)      ((a) > (b) ? (a) : (b))
 
+
 struct option options[] = {
   { "add",         0, NULL, 'a'  },
-  { "normalize",   0, NULL, 'n'  },
-  { "list",        0, NULL, 'l'  },
-  { "entries",     1, NULL, 'e'  },
   { "block-size",  1, NULL, 'b'  },
-  { "version",     0, NULL, 1001 },
+  { "entries",     1, NULL, 'e'  },
   { "help",        0, NULL, 'h'  },
+  { "list",        0, NULL, 'l'  },
+  { "normalize",   0, NULL, 'n'  },
+  { "verbose",     0, NULL, 'v'  },
+  { "version",     0, NULL, 1001 },
+  { "overlap",     0, NULL, 1002 },
+  { "no-overlap",  0, NULL, 1003 },
+  { "align-1m",    0, NULL, 1004 },
+  { "no-align-1m", 0, NULL, 1005 },
+  { "force",       0, NULL, 1006 },
+  { "try",         0, NULL, 1007 },
   { }
 };
 
@@ -44,10 +49,16 @@
   unsigned add:1;
   unsigned normalize:1;
   unsigned list:1;
+  unsigned overlap:1;
+  unsigned align_1m:1;
+  unsigned force:1;
+  unsigned _try:1;
+  unsigned help:1;
   unsigned verbose;
   unsigned block_shift;
   unsigned entries;
-} opt;
+} opt = { .overlap = 1 };
+
 
 typedef struct {
   uint8_t *ptr;
@@ -55,19 +66,25 @@
 } data_t;
 
 typedef struct {
+  uint64_t start;                      // bytes
+  data_t data;
+} cache_t;
+
+typedef struct {
   char *name;
   int fd;
   struct {
     unsigned blockdev:1;
   } is;
   unsigned block_shift;
-  uint64_t size;
+  uint64_t size;                       // bytes
+  cache_t cache[2];                    // for primary & backup gpt
 } disk_t;
 
 typedef struct {
   uint64_t signature;
   uint32_t revision;
-  uint32_t header_size;
+  uint32_t header_size;                        // bytes
   uint32_t header_crc;
   uint32_t reserved;
   uint64_t current_lba;
@@ -77,7 +94,7 @@
   uuid_t disk_guid;
   uint64_t partition_lba;
   uint32_t partition_entries;
-  uint32_t partition_entry_size;
+  uint32_t partition_entry_size;       // bytes
   uint32_t partition_crc;
 } gpt_header_t;
 
@@ -97,9 +114,10 @@
   data_t entry_blocks;
   gpt_header_t header;
   unsigned used_entries;
-  unsigned table_size;                 // in bytes
+  unsigned table_size;                 // bytes
   uint64_t min_used_lba, max_used_lba;
   unsigned block_shift;
+  unsigned next_block_shift;
   unsigned ok:1;
 } gpt_t;
 
@@ -107,73 +125,57 @@
   data_t pmbr_block;
   gpt_t primary[BLOCK_SIZES];
   gpt_t backup[BLOCK_SIZES];
-  uint64_t start_used, end_used;       // in bytes
+  uint64_t disk_size;                  // bytes
+  uint64_t start_used, end_used;       // bytes
+  uint64_t primary_end;                        // bytes
+  uint64_t backup_start;               // bytes
   unsigned used_entries;
+  unsigned min_block_shift;
+  unsigned max_block_shift;
+  unsigned gpts;
 } gpt_list_t;
 
+
 void help(void);
 int get_disk_properties(disk_t *disk);
-int get_gpt_list(disk_t *disk, gpt_list_t *gpt_list);
-gpt_entry_t get_gpt_entry(gpt_t *gpt, unsigned idx);
-gpt_t get_gpt(disk_t *disk, unsigned block_shift, uint64_t start_block);
 void free_data(data_t *data);
 void free_gpt(gpt_t *gpt);
 data_t read_disk(disk_t *disk, uint64_t start, unsigned len);
 int write_disk(disk_t *disk, uint64_t start, data_t *data);
+int flush_cache(disk_t *disk);
+int write_cache(disk_t *disk, uint64_t start, data_t *data);
 data_t clone_data(data_t *data);
 void resize_data(data_t *data, unsigned len);
 gpt_t clone_gpt(gpt_t *gpt, unsigned block_shift);
-int add_gpt(disk_t *disk, gpt_list_t *gpt_list, unsigned block_shift);
-int normalize_gpt(disk_t *disk, gpt_list_t *gpt_list, unsigned block_shift);
-int calculate_gpt_list(disk_t *disk, gpt_list_t *gpt_list);
-int write_pmbr(disk_t *disk, gpt_list_t *gpt_list);
+int parse_gpt_list(gpt_list_t *gpt_list, unsigned reparse);
+int get_gpt_list(disk_t *disk, gpt_list_t *gpt_list);
+gpt_t get_gpt(disk_t *disk, unsigned block_shift, uint64_t start_block);
+void get_gpt_used_area(gpt_t *gpt);
+gpt_entry_t get_gpt_entry(gpt_t *gpt, unsigned idx);
+int add_gpt(gpt_list_t *gpt_list, unsigned block_shift);
+int normalize_gpt(gpt_list_t *gpt_list, unsigned block_shift);
+int calculate_gpt_list(gpt_list_t *gpt_list);
 int write_gpt(disk_t *disk, gpt_t *gpt);
 int write_gpt_list(disk_t *disk, gpt_list_t *gpt_list);
 void update_gpt(gpt_t *gpt);
-void update_pmbr(disk_t *disk, gpt_list_t *gpt_list);
-
+void update_pmbr(gpt_list_t *gpt_list);
 uint32_t chksum_crc32(void *buf, unsigned len);
-
-uint16_t get_uint16_le(uint8_t *buf);
 uint32_t get_uint32_le(uint8_t *buf);
 uint64_t get_uint64_le(uint8_t *buf);
-
-void put_uint16_le(uint8_t *buf, uint16_t val);
 void put_uint32_le(uint8_t *buf, uint32_t val);
 void put_uint64_le(uint8_t *buf, uint64_t val);
+uint64_t align_down(uint64_t val, unsigned bits);
+uint64_t align_up(uint64_t val, unsigned bits);
+
 
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 int main(int argc, char **argv)
 {
-  int i;
-  extern int optind;
-  extern int opterr;
-
-  opterr = 0;
-
-  while((i = getopt_long(argc, argv, "anlhb:e:", options, NULL)) != -1) {
+  for(int i = opterr = 0; (i = getopt_long(argc, argv, "ab:e:hlnv", options, 
NULL)) != -1; ) {
     switch(i) {
       case 'a':
         opt.add = 1;
         break;
 
-      case 'n':
-        opt.normalize = 1;
-        break;
-
-      case 'l':
-        opt.list = 1;
-        break;
-
-      case 'e':
-        int entries = atoi(optarg);
-        if(entries < 4 || entries > 1024) {
-          fprintf(stderr, "unsupported number of partition entries: %d\n", 
entries);
-          return 1;
-        }
-        opt.entries = (unsigned) entries;
-        break;
-
       case 'b':
         int block_size = atoi(optarg);
         for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
@@ -188,20 +190,78 @@
         }
         break;
 
+      case 'e':
+        int entries = atoi(optarg);
+        if(entries < 4 || entries > 1024) {
+          fprintf(stderr, "unsupported number of partition entries: %d\n", 
entries);
+          return 1;
+        }
+        opt.entries = (unsigned) entries;
+        break;
+
+      case 'f':
+        opt.force = 1;
+        break;
+
+      case 'h':
+        opt.help = 1;
+        break;
+
+      case 'l':
+        opt.list = 1;
+        break;
+
+      case 'n':
+        opt.normalize = 1;
+        break;
+
+      case 'v':
+        opt.verbose++;
+        break;
+
       case 1001:
         printf(VERSION "\n");
         return 0;
         break;
 
+      case 1002:
+        opt.overlap = 1;
+        break;
+
+      case 1003:
+        opt.overlap = 0;
+        break;
+
+      case 1004:
+        opt.align_1m = 1;
+        break;
+
+      case 1005:
+        opt.align_1m = 0;
+        break;
+
+      case 1006:
+        opt.force = 1;
+        break;
+
+      case 1007:
+        opt._try = 1;
+        break;
+
       default:
         help();
-        return i == 'h' ? 0 : 1;
+        return 1;
     }
   }
 
   argc -= optind;
   argv += optind;
 
+  if(opt.help) {
+    help();
+    return 0;
+  }
+
   if(argc != 1 || !(opt.list || opt.add || opt.normalize)) {
     help();
     return 1;
@@ -209,14 +269,14 @@
 
   disk_t disk = { .name = argv[0] };
 
-  if(get_disk_properties(&disk)) {
+  if(!get_disk_properties(&disk)) {
     fprintf(stderr, "%s: failed to get disk properties\n", disk.name);
     return 1;
   }
 
   gpt_list_t gpt_list = { };  
 
-  if(get_gpt_list(&disk, &gpt_list)) {
+  if(!get_gpt_list(&disk, &gpt_list)) {
     fprintf(stderr, "unsupported partition table setup\n");
     return 1;
   }
@@ -225,12 +285,12 @@
 
   if(opt.add || opt.normalize) {
     if(opt.add) {
-      if(!add_gpt(&disk, &gpt_list, opt.block_shift ?: 12)) return 1;
+      if(!add_gpt(&gpt_list, opt.block_shift ?: 12)) return 1;
     }
-    else {
-      if(!normalize_gpt(&disk, &gpt_list, opt.block_shift)) return 1;
+    if(opt.normalize) {
+      if(!normalize_gpt(&gpt_list, opt.block_shift ?: disk.block_shift)) 
return 1;
     }
-    if(!calculate_gpt_list(&disk, &gpt_list)) {
+    if(!calculate_gpt_list(&gpt_list)) {
       fprintf(stderr, "error calculating new gpt layout\n");
       return 1;
     }
@@ -262,10 +322,13 @@
     "                        for which there is a GPT.\n"
     "  -b, --block-size N    Block size to use. Possible values are 512, 1024, 
2048, and 4096.\n"
     "  -e, --entries N       Create GPT with N partition slots (default: 
128).\n"
+    "                        Decrease the value if there is not enough free 
space on disk.\n"
+    "  -v, --verbose         Increase log level.\n"
     "  --version             Show version.\n"
     "  --help                Print this help text.\n"
     "\n"
-    "This tool takes a disk device or disk image file with a valid GPT and 
adds a valid GPT\n"
+    "%s%s"
+    "unify-gpt takes a disk device or disk image file with a valid GPT and 
adds a valid GPT\n"
     "for the specified block size. This allows you to have valid GPTs for 
multiple block sizes.\n"
     "\n"
     "The purpose is to be able to prepare disk images suitable for several 
block sizes. Once\n"
@@ -273,7 +336,7 @@
     "\n"
     "Existing partitions are kept. Partitions must be aligned to the requested 
block size.\n"
     "\n"
-    "You can run the tool several times to support more block sizes.\n"
+    "You can run unify-gpt several times to support more block sizes.\n"
     "\n"
     "The additional GPTs need extra space and partitioning tools may notify 
you that not the\n"
     "entire disk space is used.\n"
@@ -281,18 +344,35 @@
     "Note: since partitioning tools will update only the GPT for a specific 
block size, your\n"
     "partition setup will get out of sync. Use the '--normalize' option to 
remove the extra GPTs\n"
     "and keep only a single GPT for the desired block size before running a 
partitioning tool.\n"
+    , opt.verbose >= 1 ?
+    "Extended options:\n"
+    "  --force               If partition ends are not aligned for a new block 
size, round up.\n"
+    "                        Note that the partition size is only adjusted in 
the GPT for the\n"
+    "                        requested new block size.\n"
+    "  --try                 Do everything but do not write changes back to 
disk.\n"
+    "  --align-1m            Align start of usable space to 1 MiB boundary.\n"
+    "  --no-align-1m         Maximize usable space (default).\n"
+    "\n"
+    : ""
+    , opt.verbose >= 2 ?
+    "Obscure options:\n"
+    "  --overlap             Layout backup GPT so that header blocks overlap. 
This ensures that\n"
+    "                        the backup GPT header is in the last disk block 
(default).\n"
+    "  --no-overlap          Layout backup GPT so that there is a separate 
header block for each\n"
+    "                        block size.\n"
+    "\n"
+    : ""
   );
 }
 
 
-
 int get_disk_properties(disk_t *disk)
 {
   disk->fd = open(disk->name, O_RDWR | O_LARGEFILE);
 
   if(disk->fd == -1) {
     perror(disk->name);
-    return 1;
+    return 0;
   }
 
   struct stat stat = {};
@@ -302,12 +382,12 @@
       disk->is.blockdev = 1;
     }
     else if((stat.st_mode & S_IFMT) != S_IFREG) {
-      return 1;
+      return 0;
     }
   }
   else {
     perror(disk->name);
-    return 1;
+    return 0;
   }
 
   unsigned block_size = 0;
@@ -315,12 +395,12 @@
   if(disk->is.blockdev) {
     if(ioctl(disk->fd, BLKSSZGET, &block_size)) {
       perror(disk->name);
-      return 1;
+      return 0;
     }
 
     if(ioctl(disk->fd, BLKGETSIZE64, &disk->size)) {
       perror(disk->name);
-      return 1;
+      return 0;
     }
   }
   else {
@@ -333,7 +413,7 @@
     }
   }
 
-  return 0;
+  return 1;
 }
 
 
@@ -361,7 +441,7 @@
 {
   data_t data = { .len = len };
 
-  // printf("disk read: start = %"PRIu64", len = %u\n", start, len);
+  if(opt.verbose >= 3) printf("reading from disk: %u bytes at %"PRIu64"\n", 
len, start);
 
   if(lseek(disk->fd, (off_t) start, SEEK_SET) != (off_t) start) {
     perror(disk->name);
@@ -388,7 +468,7 @@
 
 int write_disk(disk_t *disk, uint64_t start, data_t *data)
 {
-  // printf("disk write: start = %"PRIu64", len = %u\n", start, data->len);
+  if(opt.verbose >= 2) printf("writing to disk: %u bytes at %"PRIu64"\n", 
data->len, start);
 
   if(lseek(disk->fd, (off_t) start, SEEK_SET) != (off_t) start) {
     perror(disk->name);
@@ -404,76 +484,222 @@
 }
 
 
-int get_gpt_list(disk_t *disk, gpt_list_t *gpt_list)
+int flush_cache(disk_t *disk)
 {
-  *gpt_list = (gpt_list_t) { };
+  for(unsigned u = 0; u < sizeof disk->cache / sizeof *disk->cache; u++) {
+    cache_t *cache = &disk->cache[u];
+    if(!write_disk(disk, cache->start, &cache->data)) return 0;
+  }
 
-  unsigned gpts_ok = 0, gpts_bad = 0;
+  return 1;
+}
 
-  gpt_list->pmbr_block = read_disk(disk, 0, 1 << MIN_BLOCK_SHIFT);
 
-  if(!gpt_list->pmbr_block.len) return 1;
+int write_cache(disk_t *disk, uint64_t start, data_t *data)
+{
+  if(opt.verbose >= 3) printf("writing to cache: %u bytes at %"PRIu64"\n", 
data->len, start);
+
+  for(unsigned u = 0; u < sizeof disk->cache / sizeof *disk->cache; u++) {
+    cache_t *cache = &disk->cache[u];
+    if(start >= cache->start) {
+      start -= cache->start;
+      if(start + data->len <= cache->data.len) {
+        memcpy(cache->data.ptr + start, data->ptr, data->len);
+        return 1;
+      }
+    }
+  }
+
+  fprintf(stderr, "oops: invalid write attempt prevented\n");
+
+  return 0;
+}
+
+
+data_t clone_data(data_t *data)
+{
+  data_t new_data = { .len = data->len };
+
+  if(!new_data.len) return new_data;
+
+  new_data.ptr = malloc(new_data.len);
+
+  if(!new_data.ptr) {
+    new_data.len = 0;
+    return new_data;
+  }
+
+  memcpy(new_data.ptr, data->ptr, new_data.len);
+
+  return new_data;
+}
+
+
+void resize_data(data_t *data, unsigned len)
+{
+  if(len <= data->len) {
+    data->len = len;
+    return;
+  }
+
+  data->ptr = realloc(data->ptr, len);
+
+  if(data->ptr) {
+    memset(data->ptr + data->len, 0, len - data->len);
+    data->len = len;
+  }
+}
 
-  gpt_list->start_used--;
 
-  for(unsigned u = 0; u < BLOCK_SIZES; u++) {
-    gpt_list->primary[u] = get_gpt(disk, MIN_BLOCK_SHIFT + u, 1);
-    if(gpt_list->primary[u].ok) {
-      gpt_list->start_used = MIN(gpt_list->start_used, 
gpt_list->primary[u].min_used_lba << gpt_list->primary[u].block_shift);
-      gpt_list->end_used = MAX(gpt_list->end_used, 
(gpt_list->primary[u].max_used_lba + 1) << gpt_list->primary[u].block_shift);
-      gpt_list->used_entries = MAX(gpt_list->used_entries, 
gpt_list->primary[u].used_entries);
-
-      gpt_list->backup[u] = get_gpt(disk, MIN_BLOCK_SHIFT + u, 
gpt_list->primary[u].header.backup_lba);
-      printf("existing gpt: block size %u, partitions %u", 1 << 
gpt_list->primary[u].block_shift, gpt_list->primary[u].used_entries);
-      if(gpt_list->backup[u].ok) {
-        gpts_ok++;
+//
+// The cloned gpt will have the partition entries corrected to use the new
+// block size.
+//
+// But the gpt header data are totally wrong. They will be filled in
+// correctly in calculate_gpt_list().
+//
+gpt_t clone_gpt(gpt_t *gpt, unsigned block_shift)
+{
+  gpt_t new_gpt = { };
+
+  new_gpt = *gpt;
+
+  new_gpt.ok = 0;
+
+  new_gpt.header_block = clone_data(&gpt->header_block);
+  new_gpt.entry_blocks = clone_data(&gpt->entry_blocks);
+
+  if(!new_gpt.header_block.len || !new_gpt.entry_blocks.len) return new_gpt;
+
+  unsigned block_mask = (1u << block_shift) - 1;
+
+  for(unsigned u = 0; u < new_gpt.used_entries; u++) {
+    uint8_t *entry = new_gpt.entry_blocks.ptr + u * 
new_gpt.header.partition_entry_size;
+    // start
+    uint64_t lba = get_uint64_le(entry + 32);
+    if(lba) {
+      lba <<= gpt->block_shift;
+      if((lba & block_mask)) {
+        fprintf(stderr, "gpt_%u: partition %u has misaligned start\n", 1 << 
block_shift, u + 1);
+        return new_gpt;
       }
-      else {
-        gpts_bad++;
-        printf(" - but no backup gpt");
+      put_uint64_le(entry + 32, lba >> block_shift);
+    }
+
+    // end
+    lba = get_uint64_le(entry + 40);
+    if(lba) {
+      lba = (lba + 1) << gpt->block_shift;
+      if((lba & block_mask)) {
+        fprintf(stderr, "gpt_%u: partition %u has misaligned end%s\n",
+          1 << block_shift, u + 1, opt.force ? " - rounding up" : " - use 
option '--force' to fix"
+        );
+        if(opt.force) {
+          lba = align_up(lba, block_shift);
+        }
+        else {
+          return new_gpt;
+        }
       }
-      printf("\n");
+      put_uint64_le(entry + 40, (lba - 1) >> block_shift);
     }
   }
 
-  // printf("gpt_list: start = %"PRIu64", end = %"PRIu64", entries = %u\n", 
gpt_list->start_used, gpt_list->end_used, gpt_list->used_entries);
+  new_gpt.block_shift = block_shift;
+  new_gpt.ok = 1;
+
+  get_gpt_used_area(&new_gpt);
 
-  return gpts_ok && !gpts_bad ? 0 : 1;
+  return new_gpt;
 }
 
 
-gpt_entry_t get_gpt_entry(gpt_t *gpt, unsigned idx)
+int parse_gpt_list(gpt_list_t *gpt_list, unsigned reparse)
 {
-  gpt_entry_t entry = { };
+  unsigned gpts_bad = 0;
 
-  if(
-    idx >= gpt->header.partition_entries ||
-    gpt->header.partition_entry_size * (idx + 1) > gpt->entry_blocks.len
-  ) return entry;
+  unsigned show = opt.verbose + (reparse ? 0 : 1);
 
-  uint8_t *buf = gpt->entry_blocks.ptr + gpt->header.partition_entry_size * 
idx;
+  if(opt.verbose >= 1) printf("parsing gpt list%s\n", reparse ? " again" : "");
 
-  memcpy(entry.type_guid, buf, sizeof entry.type_guid);
-  memcpy(entry.partition_guid, buf, sizeof entry.partition_guid);
-  entry.first_lba = get_uint64_le(buf + 32);
-  entry.last_lba = get_uint64_le(buf + 40);
-  entry.attributes = get_uint64_le(buf + 48);
-  memcpy(entry.name, buf + 56, sizeof entry.name);
+  gpt_list->start_used = -1ull;
+  gpt_list->end_used = 0;
+  gpt_list->used_entries = 0;
 
-  if(entry.first_lba < entry.last_lba) {
-    entry.ok = 1;
+  gpt_list->min_block_shift = gpt_list->max_block_shift = 0;
+  gpt_list->gpts = 0;
+
+  unsigned last_idx = 0;
+  for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
+    gpt_t *gpt = &gpt_list->primary[u - MIN_BLOCK_SHIFT];
+    if(!gpt->ok) continue;
+
+    gpt_list->start_used = MIN(gpt_list->start_used, gpt->min_used_lba << 
gpt->block_shift);
+    gpt_list->end_used = MAX(gpt_list->end_used, (gpt->max_used_lba + 1) << 
gpt->block_shift);
+    gpt_list->used_entries = MAX(gpt_list->used_entries, gpt->used_entries);
+
+    gpt->next_block_shift = gpt->block_shift;
+    if(last_idx) gpt_list->primary[last_idx - 
MIN_BLOCK_SHIFT].next_block_shift = gpt->block_shift;
+    last_idx = u;
+
+    gpt_list->max_block_shift = u;
+    if(!gpt_list->min_block_shift) gpt_list->min_block_shift = u;
+
+    if(show) printf("found gpt_%u: %u partitions", 1 << gpt->block_shift, 
gpt->used_entries);
+
+    if(gpt_list->backup[u - MIN_BLOCK_SHIFT].ok) {
+      gpt_list->gpts++;
+      if(show) printf("\n");
+    }
+    else {
+      gpts_bad++;
+      if(show) printf(" - but no backup gpt\n");
+    }
   }
-  else {
-    entry.zero = 1;
-    for(unsigned u = 0 ; u < gpt->header.partition_entry_size; u++) {
-      if(buf[u]) {
-        entry.zero = 0;
-        break;
-      }
+
+  if(opt.verbose >= 2) {
+    for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
+      gpt_t *gpt = &gpt_list->primary[u - MIN_BLOCK_SHIFT];
+      if(!gpt->ok) continue;
+      printf("gpt_%u: next block size %u\n", 1 << u, 1 << 
gpt->next_block_shift);
     }
   }
 
-  return entry;
+  if(opt.verbose >= 1) {
+    printf("gpt_list: %u gpts, min block size %u, max block size %u\n",
+      gpt_list->gpts,
+      1 << gpt_list->min_block_shift,
+      1 << gpt_list->max_block_shift
+    );
+    printf("gpt_list: %u partitions, start ofs %"PRIu64", end ofs %"PRIu64" = 
-%"PRIu64"\n",
+      gpt_list->used_entries,
+      gpt_list->start_used,
+      gpt_list->end_used,
+      gpt_list->disk_size - gpt_list->end_used
+    );
+  }
+
+  return gpt_list->gpts && !gpts_bad ? 1 : 0;
+}
+
+
+int get_gpt_list(disk_t *disk, gpt_list_t *gpt_list)
+{
+  *gpt_list = (gpt_list_t) { };
+
+  gpt_list->disk_size = disk->size;
+
+  gpt_list->pmbr_block = read_disk(disk, 0, 1 << MIN_BLOCK_SHIFT);
+
+  if(!gpt_list->pmbr_block.len) return 0;
+
+  for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
+    gpt_list->primary[u - MIN_BLOCK_SHIFT] = get_gpt(disk, u, 1);
+    if(!gpt_list->primary[u - MIN_BLOCK_SHIFT].ok) continue;
+    gpt_list->backup[u - MIN_BLOCK_SHIFT] = get_gpt(disk, u, 
gpt_list->primary[u - MIN_BLOCK_SHIFT].header.backup_lba);
+  }
+
+  return parse_gpt_list(gpt_list, 0);
 }
 
 
@@ -512,7 +738,7 @@
   if(gpt_h.header_size == 92) {
     uint8_t tmp[92];
     memcpy(tmp, gpt_header_block.ptr, sizeof tmp);
-    memset(tmp + 16, 0, 4);
+    put_uint32_le(tmp + 16, 0);
     if(chksum_crc32(tmp, sizeof tmp) == gpt_h.header_crc) header_ok = 1;
   }
 
@@ -530,8 +756,6 @@
     return gpt;
   }
 
-  // printf("block_size %u: gpt head ok\n", 1 << block_shift);
-
   data_t gpt_entry_blocks = read_disk(disk, gpt_h.partition_lba << 
block_shift, gpt_h.partition_entries * gpt_h.partition_entry_size);
 
   if(!gpt_entry_blocks.len) {
@@ -547,115 +771,90 @@
     return gpt;
   }
 
-  // printf("gpt entries ok\n");
-
   gpt.header_block = gpt_header_block;
   gpt.entry_blocks = gpt_entry_blocks;
   gpt.header = gpt_h;
   gpt.block_shift = block_shift;
   gpt.ok = 1;
 
-  gpt.min_used_lba--;
-  for(unsigned u = 0; u < gpt_h.partition_entries; u++) {
-    gpt_entry_t e = get_gpt_entry(&gpt, u);
-    if(e.ok) {
-      if(e.first_lba < gpt.min_used_lba) gpt.min_used_lba = e.first_lba;
-      if(e.last_lba > gpt.max_used_lba) gpt.max_used_lba = e.last_lba;
-    }
-    if(!e.zero) gpt.used_entries = u + 1;
+  if(opt.verbose >= 2) {
+    printf("gpt_%u.header: blk %"PRIu64", backup blk %"PRIu64"\n", 1 << 
gpt.block_shift, gpt_h.current_lba, gpt_h.backup_lba);
+    printf("gpt_%u.table: %u entries max, blk %"PRIu64" - blk %"PRIu64"\n",
+      1 << gpt.block_shift,
+      gpt_h.partition_entries,
+      gpt_h.partition_lba,
+      gpt_h.partition_lba + (align_up(gpt_h.partition_entries * 
gpt_h.partition_entry_size, gpt.block_shift) >> gpt.block_shift) - 1
+    );
   }
 
-  if(!gpt.max_used_lba) gpt.min_used_lba = 0;
-
-  // printf("+++ %u entries, %"PRIu64" - %"PRIu64"\n", gpt.used_entries, 
gpt.min_used_lba, gpt.max_used_lba);
+  get_gpt_used_area(&gpt);
 
   return gpt;
 }
 
 
-data_t clone_data(data_t *data)
+void get_gpt_used_area(gpt_t *gpt)
 {
-  data_t new_data = { .len = data->len };
-
-  if(!new_data.len) return new_data;
-
-  new_data.ptr = malloc(new_data.len);
-
-  if(!new_data.ptr) {
-    new_data.len = 0;
-    return new_data;
-  }
-
-  memcpy(new_data.ptr, data->ptr, new_data.len);
-
-  return new_data;
-}
-
+  gpt->min_used_lba = -1ull;
+  gpt->max_used_lba = 0;
+  gpt->used_entries = 0;
 
-void resize_data(data_t *data, unsigned len)
-{
-  if(len <= data->len) {
-    data->len = len;
-    return;
+  for(unsigned u = 0; u < gpt->header.partition_entries; u++) {
+    gpt_entry_t e = get_gpt_entry(gpt, u);
+    if(e.ok) {
+      if(e.first_lba < gpt->min_used_lba) gpt->min_used_lba = e.first_lba;
+      if(e.last_lba > gpt->max_used_lba) gpt->max_used_lba = e.last_lba;
+      if(opt.verbose >= 2) {
+        printf("gpt_%u.part%u: blk %"PRIu64" - blk %"PRIu64"\n", 1 << 
gpt->block_shift, u + 1, e.first_lba, e.last_lba);
+      }
+    }
+    if(!e.zero) gpt->used_entries = u + 1;
   }
 
-  data->ptr = realloc(data->ptr, len);
-
-  if(data->ptr) {
-    memset(data->ptr + data->len, 0, len - data->len);
-    data->len = len;
+  if(opt.verbose >= 2) {
+    printf("gpt_%u: %u entries, blk %"PRIu64" - blk %"PRIu64"\n",
+      1 << gpt->block_shift, gpt->used_entries, gpt->min_used_lba, 
gpt->max_used_lba
+    );
   }
 }
 
 
-gpt_t clone_gpt(gpt_t *gpt, unsigned block_shift)
+gpt_entry_t get_gpt_entry(gpt_t *gpt, unsigned idx)
 {
-  gpt_t new_gpt = { };
-
-  new_gpt = *gpt;
-
-  new_gpt.ok = 0;
-
-  new_gpt.header_block = clone_data(&gpt->header_block);
-  new_gpt.entry_blocks = clone_data(&gpt->entry_blocks);
+  gpt_entry_t entry = { };
 
-  if(!new_gpt.header_block.len || !new_gpt.entry_blocks.len) return new_gpt;
+  if(
+    idx >= gpt->header.partition_entries ||
+    gpt->header.partition_entry_size * (idx + 1) > gpt->entry_blocks.len
+  ) return entry;
 
-  unsigned block_mask = (1u << block_shift) - 1;
+  uint8_t *buf = gpt->entry_blocks.ptr + gpt->header.partition_entry_size * 
idx;
 
-  for(unsigned u = 0; u < new_gpt.used_entries; u++) {
-    uint8_t *entry = new_gpt.entry_blocks.ptr + u * 
new_gpt.header.partition_entry_size;
-    // start
-    uint64_t lba = get_uint64_le(entry + 32);
-    if(lba) {
-      lba <<= gpt->block_shift;
-      if((lba & block_mask)) {
-        fprintf(stderr, "misaligned partition start: %"PRIu64"\n", lba);
-        return new_gpt;
-      }
-      put_uint64_le(entry + 32, lba >> block_shift);
-    }
+  memcpy(entry.type_guid, buf, sizeof entry.type_guid);
+  memcpy(entry.partition_guid, buf, sizeof entry.partition_guid);
+  entry.first_lba = get_uint64_le(buf + 32);
+  entry.last_lba = get_uint64_le(buf + 40);
+  entry.attributes = get_uint64_le(buf + 48);
+  memcpy(entry.name, buf + 56, sizeof entry.name);
 
-    // end
-    lba = get_uint64_le(entry + 40);
-    if(lba) {
-      lba = (lba + 1) << gpt->block_shift;
-      if((lba & block_mask)) {
-        fprintf(stderr, "misaligned partition end: %"PRIu64"\n", lba);
-        return new_gpt;
+  if(entry.first_lba < entry.last_lba) {
+    entry.ok = 1;
+  }
+  else {
+    entry.zero = 1;
+    for(unsigned u = 0 ; u < gpt->header.partition_entry_size; u++) {
+      if(buf[u]) {
+        entry.zero = 0;
+        break;
       }
-      put_uint64_le(entry + 40, (lba - 1) >> block_shift);
     }
   }
 
-  new_gpt.block_shift = block_shift;
-  new_gpt.ok = 1;
-
-  return new_gpt;
+  return entry;
 }
 
 
-int add_gpt(disk_t *disk, gpt_list_t *gpt_list, unsigned block_shift)
+int add_gpt(gpt_list_t *gpt_list, unsigned block_shift)
 {
   if(gpt_list->primary[block_shift - MIN_BLOCK_SHIFT].ok) {
     fprintf(stderr, "gpt for block size %u already exists\n", 1 << 
block_shift);
@@ -674,103 +873,92 @@
     break;
   }
 
-  printf("adding gpt: block size %u\n", 1 << block_shift);
+  printf("adding gpt_%u\n", 1 << block_shift);
 
-  return 1;
+  // re-parse needed for data from added GPT
+  return parse_gpt_list(gpt_list, 1);
 }
 
 
-int normalize_gpt(disk_t *disk, gpt_list_t *gpt_list, unsigned block_shift)
+int normalize_gpt(gpt_list_t *gpt_list, unsigned block_shift)
 {
-  unsigned gpts = 0;
-
-  if(!block_shift) block_shift = disk->block_shift;
-
-  for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
-    gpt_t *gpt = &gpt_list->primary[u - MIN_BLOCK_SHIFT];
-    if(!gpt->ok) continue;
-    gpts++;
-    if(!block_shift) block_shift = u;
-  }
+  if(!block_shift) block_shift = gpt_list->min_block_shift;
 
-  if(gpts == 1) {
-    fprintf(stderr, "nothing to do: single gpt for block size %u\n", 1 << 
block_shift);
+  if(gpt_list->gpts == 1 && !opt.force && !opt.entries) {
+    fprintf(stderr, "nothing to do: single gpt for block size %u\n", 1 << 
gpt_list->min_block_shift);
     return 0;
   }
 
-  if(gpts == 0 || !gpt_list->primary[block_shift - MIN_BLOCK_SHIFT].ok) {
+  if(!gpt_list->primary[block_shift - MIN_BLOCK_SHIFT].ok) {
     fprintf(stderr, "gpt for block size %u does not exist\n", 1 << 
block_shift);
     return 0;
   }
 
-  printf("keeping gpt: block size %u\n", 1 << block_shift);
-
   for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
     gpt_t *gpt = &gpt_list->primary[u - MIN_BLOCK_SHIFT];
-    if(u != block_shift) gpt->ok = 0;
+    if(gpt->ok && u != block_shift) {
+      printf("deleting gpt_%u\n", 1 << u);
+      gpt->ok = 0;
+    }
     gpt = &gpt_list->backup[u - MIN_BLOCK_SHIFT];
-    if(u != block_shift) gpt->ok = 0;
+    if(gpt->ok && u != block_shift) gpt->ok = 0;
   }
 
-  return 1;
+  printf("keeping gpt_%u\n", 1 << block_shift);
+
+  // re-parse - data from deleted GPTs should not influence the result
+  return parse_gpt_list(gpt_list, 1);
 }
 
 
-int calculate_gpt_list(disk_t *disk, gpt_list_t *gpt_list)
+int calculate_gpt_list(gpt_list_t *gpt_list)
 {
   unsigned entries = opt.entries ?: 128;
 
-  if(entries < gpt_list->used_entries) {
-    fprintf(stderr, "new gpt must have at least %u entries\n", 
gpt_list->used_entries);
-    return 0;
-  }
-
-  unsigned max_shift = MIN_BLOCK_SHIFT;
-
-  for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
-    gpt_t *gpt = &gpt_list->primary[u - MIN_BLOCK_SHIFT];
-    if(!gpt->ok) continue;
+  entries = MAX(entries, gpt_list->used_entries);
 
-    max_shift = u;
+  if(opt.verbose >= 2) {
+    printf("calculating layout: %u entries max, disk size %"PRIu64"\n", 
entries, gpt_list->disk_size);
   }
 
-  unsigned entry_align_mask = (1u << (max_shift - 7)) - 1;
-  entries = (entries + entry_align_mask) & ~entry_align_mask;
-
-  uint64_t table_end = disk->size;
+  uint64_t table_end = gpt_list->disk_size;
 
   // 1st: backup gpt header location, down from disk end
   for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
     gpt_t *gpt = &gpt_list->primary[u - MIN_BLOCK_SHIFT];
     if(!gpt->ok) continue;
 
-    uint64_t block_mask = (1 << u) - 1u;
-
-    table_end &= ~block_mask;
-    table_end -= (1 << u);
+    table_end = align_down(opt.overlap ? gpt_list->disk_size : table_end, u);
+    table_end -= 1 << u;
 
     gpt->header.backup_lba = table_end >> u;
+
+    if(opt.verbose >= 2) {
+      printf("gpt_%u.header: blk 1 (%u), backup blk %"PRIu64" (%"PRIu64" = 
-%"PRIu64")\n",
+        1 << u,
+        1 << u,
+        gpt->header.backup_lba,
+        gpt->header.backup_lba << u,
+        gpt_list->disk_size - (gpt->header.backup_lba << u)
+      );
+    }
   }
 
-  uint64_t table_ofs = 2 << max_shift;
+  uint64_t table_ofs = 2 << gpt_list->max_block_shift;
 
-  //2nd: partition_lba up from start for primary gpt, and down from end for 
backup gpt
+  // 2nd: partition_lba up from start for primary gpt, and down from end for 
backup gpt
   for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
     gpt_t *gpt = &gpt_list->primary[u - MIN_BLOCK_SHIFT];
     if(!gpt->ok) continue;
 
-    // printf("+++ blk %u: disk size = %"PRIu64"\n", 1 << u, disk->size >> u);
-
-    uint64_t block_mask = (1 << u) - 1u;
-
-    table_ofs = (table_ofs + block_mask) & ~block_mask;
+    table_ofs = align_up(table_ofs, u);
 
-    uint64_t table_size = ((entries << 7) + block_mask) & ~block_mask;
-    // unsigned real_entries = table_size >> 7;
+    uint64_t table_size = align_up(entries << 7, gpt->next_block_shift);
+    unsigned real_entries = table_size >> 7;
 
     // primary
 
-    gpt->header.partition_entries = entries;
+    gpt->header.partition_entries = real_entries;
     gpt->table_size = table_size;
     gpt->header.current_lba = 1;
     gpt->header.partition_lba = table_ofs >> u;
@@ -779,13 +967,24 @@
     resize_data(&gpt->entry_blocks, table_size);
 
     if(!gpt->header_block.ptr || !gpt->entry_blocks.ptr) {
-      fprintf(stderr, "add gpt: out of memory\n");
+      fprintf(stderr, "malloc: out of memory\n");
       return 0;
     }
 
+    gpt_list->primary_end = table_ofs;
+
     gpt->header.partition_crc = chksum_crc32(gpt->entry_blocks.ptr, 
gpt->entry_blocks.len);
 
-    // printf("   prim partition_lba = %"PRIu64", table_size = %"PRIu64" 
(%u)\n", gpt->header.partition_lba, table_size, (unsigned) table_size >> u);
+    if(opt.verbose >= 2) {
+      printf("gpt_%u.primary_table: %u entries max, blk %"PRIu64" (ofs 
%"PRIu64") - blk %"PRIu64" (ofs %"PRIu64")\n",
+        1 << u,
+        gpt->header.partition_entries,
+        gpt->header.partition_lba,
+        gpt->header.partition_lba << u,
+        gpt->header.partition_lba + (table_size >> u) - 1,
+        (gpt->header.partition_lba + (table_size >> u) - 1) << u
+      );
+    }
 
     uint64_t backup_lba = gpt->header.backup_lba;
 
@@ -793,12 +992,12 @@
 
     gpt = &gpt_list->backup[u - MIN_BLOCK_SHIFT];
 
-    gpt->header.partition_entries = entries;
+    gpt->header.partition_entries = real_entries;
     gpt->table_size = table_size;
     gpt->header.current_lba = backup_lba;
     gpt->header.backup_lba = 1;
 
-    table_end &= ~block_mask;
+    table_end = align_down(table_end, u);
 
     gpt->header.partition_lba = (table_end - table_size) >> u;
 
@@ -806,35 +1005,60 @@
     resize_data(&gpt->entry_blocks, table_size);
 
     if(!gpt->header_block.ptr || !gpt->entry_blocks.ptr) {
-      fprintf(stderr, "add gpt: out of memory\n");
+      fprintf(stderr, "malloc: out of memory\n");
       return 0;
     }
 
     gpt->header.partition_crc = chksum_crc32(gpt->entry_blocks.ptr, 
gpt->entry_blocks.len);
 
-    // printf("   back partition_lba = %"PRIu64", back lba = %"PRIu64"\n", 
gpt->header.partition_lba, gpt->header.current_lba);
+    if(opt.verbose >= 2) {
+      printf("gpt_%u.backup_table: %u entries max, blk %"PRIu64" (ofs 
%"PRIu64" = -%"PRIu64") - blk %"PRIu64" (ofs %"PRIu64" = -%"PRIu64")\n",
+        1 << u,
+        gpt->header.partition_entries,
+        gpt->header.partition_lba,
+        gpt->header.partition_lba << u,
+        gpt_list->disk_size - (gpt->header.partition_lba << u),
+        gpt->header.partition_lba + (table_size >> u) - 1,
+        (gpt->header.partition_lba + (table_size >> u) - 1) << u,
+        gpt_list->disk_size - ((gpt->header.partition_lba + (table_size >> u) 
- 1) << u)
+      );
+    }
 
     table_ofs += table_size;
     table_end -= table_size;
-  }
 
-  if(table_ofs > gpt_list->start_used || table_end < gpt_list->end_used) {
-    fprintf(stderr, "not enough space for primary gpt\n");
-    return 0;
+    gpt_list->primary_end = table_ofs;
+    gpt_list->backup_start = table_end;
   }
 
   uint64_t first_free = table_ofs;
   uint64_t end_free = table_end;
 
-  uint64_t align_mask = (1u << max_shift) - 1;
+  first_free = align_up(first_free, gpt_list->max_block_shift);
+  end_free = align_down(end_free, gpt_list->max_block_shift);
+
+  if(opt.align_1m) {
+    uint64_t first_free_1mb = align_up(first_free, 20);
+    if(gpt_list->start_used >= first_free_1mb) {
+      first_free = first_free_1mb;
+    }
+  }
 
-  first_free = (first_free + align_mask) & ~align_mask;
-  end_free = end_free & ~align_mask;
+  if(opt.verbose >= 2) {
+    printf("gpt_list: free start ofs %"PRIu64", free end ofs %"PRIu64" = 
-%"PRIu64"\n", first_free, end_free, gpt_list->disk_size - end_free);
+  }
 
-  // FIXME: align first_free to 1 MiB?
-#if 0
-  align_mask = (1 << 20) - 1;
-#endif
+  uint64_t needed = 0;
+  if(first_free > gpt_list->start_used) needed = first_free - 
gpt_list->start_used;
+  if(end_free < gpt_list->end_used) needed = MAX(needed, gpt_list->end_used - 
end_free);
+
+  if(needed) {
+    fprintf(stderr,
+      "not enough free space (%.10g kiB needed) for gpt - try option 
'--entries' to reduce GPT size\n",
+      (double) needed / 1024
+    );
+    return 0;
+  }
 
   // 3rd: set first_lba, last_lba
   for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
@@ -856,92 +1080,57 @@
     update_gpt(&gpt_list->backup[u - MIN_BLOCK_SHIFT]);
   }
 
-  update_pmbr(disk, gpt_list);
+  update_pmbr(gpt_list);
 
   return 1;
 }
 
 
-int write_pmbr(disk_t *disk, gpt_list_t *gpt_list)
+int write_gpt(disk_t *disk, gpt_t *gpt)
 {
-  data_t pmbr = clone_data(&gpt_list->pmbr_block);
-
-  resize_data(&pmbr, 1 << MAX_BLOCK_SHIFT);
+  if(!gpt->ok) return 1;
 
-  write_disk(disk, 0, &pmbr);
+  if(!write_cache(disk, gpt->header.current_lba << gpt->block_shift, 
&gpt->header_block)) return 0;
 
-  free_data(&pmbr);
+  if(!write_cache(disk, gpt->header.partition_lba << gpt->block_shift, 
&gpt->entry_blocks)) return 0;
 
   return 1;
 }
 
 
-int write_gpt(disk_t *disk, gpt_t *gpt)
+int write_gpt_list(disk_t *disk, gpt_list_t *gpt_list)
 {
-  int ok = 1;
+  // prepare cache
+  disk->cache[0] = (cache_t) {
+    .start = 0,
+    .data = { .len = gpt_list->primary_end, .ptr = calloc(1, 
gpt_list->primary_end) }
+  };
+
+  disk->cache[1] = (cache_t) {
+    .start = gpt_list->backup_start,
+    .data = { .len = gpt_list->disk_size - gpt_list->backup_start, .ptr = 
calloc(1, gpt_list->disk_size - gpt_list->backup_start) }
+  };
 
-  if(!gpt->ok) return ok;
-
-  if(!write_disk(disk, gpt->header.current_lba << gpt->block_shift, 
&gpt->header_block)) {
-    ok = 0;
-    return ok;
-  }
-
-  if(!write_disk(disk, gpt->header.partition_lba << gpt->block_shift, 
&gpt->entry_blocks)) {
-    ok = 0;
-    return ok;
+  if(!disk->cache[0].data.ptr || !disk->cache[1].data.ptr) {
+    fprintf(stderr, "malloc: out of memory\n");
+    return 0;
   }
 
-  return ok;
-}
-
-
-int write_gpt_list(disk_t *disk, gpt_list_t *gpt_list)
-{
-  int ok = 1;
-
-  write_pmbr(disk, gpt_list);
+  // update pmbr
+  if(!write_cache(disk, 0, &gpt_list->pmbr_block)) return 0;
 
+  // primary tables
   for(unsigned u = MIN_BLOCK_SHIFT; u <= MAX_BLOCK_SHIFT; u++) {
-    if(!write_gpt(disk, &gpt_list->primary[u - MIN_BLOCK_SHIFT])) {
-      ok = 0;
-      break;
-    }
-    if(!write_gpt(disk, &gpt_list->backup[u - MIN_BLOCK_SHIFT])) {
-      ok = 0;
-      break;
-    }
+    if(!write_gpt(disk, &gpt_list->primary[u - MIN_BLOCK_SHIFT])) return 0;
   }
 
-  return ok;
-}
-
-
-void update_pmbr(disk_t *disk, gpt_list_t *gpt_list)
-{
-  unsigned min_shift = MAX_BLOCK_SHIFT;
-  unsigned gpts = 0;
-
+  // backup tables in reverse order
   for(unsigned u = MAX_BLOCK_SHIFT; u >= MIN_BLOCK_SHIFT; u--) {
-    gpt_t *gpt = &gpt_list->primary[u - MIN_BLOCK_SHIFT];
-    if(!gpt->ok) continue;
-
-    min_shift = u;
-    gpts++;
+    if(!write_gpt(disk, &gpt_list->backup[u - MIN_BLOCK_SHIFT])) return 0;
   }
 
-  uint8_t *buf = gpt_list->pmbr_block.ptr + 446;
-
-  if(buf[4] == 0xee) {
-    // CHS + type
-    put_uint32_le(buf + 4, 0xffffffee);
-
-    uint64_t size = (disk->size >> min_shift) - 1;
-
-    if(gpts != 1 || size > 0xffffffff) size = 0xffffffff;
-
-    put_uint32_le(buf + 12, size);
-  }
+  // write to disk
+  return opt._try ? 1 : flush_cache(disk);
 }
 
 
@@ -964,6 +1153,23 @@
 }
 
 
+void update_pmbr(gpt_list_t *gpt_list)
+{
+  uint8_t *buf = gpt_list->pmbr_block.ptr + 446;
+
+  if(buf[4] == 0xee) {
+    // CHS + type
+    put_uint32_le(buf + 4, 0xffffffee);
+
+    uint64_t size = (gpt_list->disk_size >> gpt_list->min_block_shift) - 1;
+
+    if(gpt_list->gpts != 1 || size > 0xffffffffull) size = 0xffffffff;
+
+    put_uint32_le(buf + 12, size);
+  }
+}
+
+
 uint32_t chksum_crc32(void *buf, unsigned len)
 {
   uint8_t *bytes = buf;
@@ -980,12 +1186,6 @@
 }
 
 
-uint16_t get_uint16_le(uint8_t *buf)
-{
-  return (buf[1] << 8) + buf[0];
-}
-
-
 uint32_t get_uint32_le(uint8_t *buf)
 {
   return ((uint32_t) buf[3] << 24) + (buf[2] << 16) + (buf[1] << 8) + buf[0];
@@ -998,13 +1198,6 @@
 }
 
 
-void put_uint16_le(uint8_t *buf, uint16_t val)
-{
-  buf[0] = val;
-  buf[1] = val >> 8;
-}
-
-
 void put_uint32_le(uint8_t *buf, uint32_t val)
 {
   buf[0] = val;
@@ -1019,3 +1212,19 @@
   put_uint32_le(buf, val);
   put_uint32_le(buf + 4, val >> 32);
 }
+
+
+uint64_t align_down(uint64_t val, unsigned bits)
+{
+  uint64_t mask = (1u << bits) - 1;
+
+  return val & ~mask;
+}
+
+
+uint64_t align_up(uint64_t val, unsigned bits)
+{
+  uint64_t mask = (1u << bits) - 1;
+
+  return (val + mask) & ~mask;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/parti-2.7/unify-gpt_man.adoc 
new/parti-2.8/unify-gpt_man.adoc
--- old/parti-2.7/unify-gpt_man.adoc    1970-01-01 01:00:00.000000000 +0100
+++ new/parti-2.8/unify-gpt_man.adoc    2025-05-09 17:41:25.000000000 +0200
@@ -0,0 +1,125 @@
+= unify-gpt(1)
+:doctype: manpage
+:manmanual: User Commands
+:mansource: unify-gpt {version}
+
+== Name
+
+unify-gpt - Create unified GPT for multiple block sizes.
+
+
+== Synopsis
+
+*unify-gpt* [_OPTIONS_] _DEVICE|IMAGE_
+
+
+== Description
+
+unify-gpt takes a disk device or disk image file with a valid GPT and adds a 
valid GPT
+for the specified block size. This allows you to have valid GPTs for multiple 
block sizes.
+
+The purpose is to be able to prepare disk images suitable for several block 
sizes.
+
+Once the image is in use, you can remove the extra GPTs using '--normalize'.
+
+Existing partitions are kept. Partitions must be aligned to the requested 
block size.
+
+You can run unify-gpt several times to support more block sizes.
+
+The additional GPTs need extra space and partitioning tools may notify you 
that not the
+entire disk space is used.
+
+*Note*: since partitioning tools will update only the GPT for a specific block 
size, your
+partition setup will get out of sync. Use the '--normalize' option to remove 
the extra GPTs
+and keep only a single GPT for the desired block size before running a 
partitioning tool.
+
+=== General Options
+
+*-l, --list*::
+Show current GPT setup.
+
+*-a, --add*::
+Add GPT for the specified block size (default: 4096).
+
+*-n, --normalize*::
+Normaize GPT. This removes additional GPTs and keeps only a single GPT.
+The default block size for block devices is the device block size.
+The default block size for image files is the smallest block size for which 
there is a GPT.
+
+*-b, --block-size*=_N_::
+Block size to use. Possible values are 512, 1024, 2048, and 4096.
+
+*-e, --entries*=_N_::
+Create GPT with N partition slots (default: 128).
+Decrease the value if there is not enough free space on disk.
+
+*--verbose*::
+Show more detailed messages. Can be repeated to log even more.
+
+*--version*::
+Show unify-gpt version.
+
+*--help*::
+Show help text.
+
+=== Extended Options
+
+*--force*::
+If partition ends are not aligned for a new block size, round up.
+Note that the partition size is only adjusted in the GPT for the requested new 
block size.
+
+*--try*::
+Do everything but do not write changes back to disk.
+
+*--overlap*::
+Layout backup GPT so that header blocks overlap.
+This ensures that the backup GPT header is in the last disk block (default).
+
+*--no-overlap*::
+Layout backup GPT so that there is a separate header block for each block size.
+
+*--align-1m*::
+Align start of usable space to 1 MiB boundary.
+
+*--no-align-1m*::
+Maximize usable space (default).
+
+== Examples
+
+=== Basic use
+
+--
+# add a GPT for 4096 byte blocks to an existing 512 byte block size GPT +
+unify-gpt --block-size 4096 --add foo.img
+
+# add a GPT for 4096 byte blocks but reduce GPT size in cases +
+# where the GPT would get too big +
+unify-gpt --block-size 4096 --add --entries 32 foo.img
+
+# remove all GPTs except the one for the actual block size of /dev/sda +
+unify-gpt --normalize /dev/sda
+--
+
+=== Advanced use
+
+--
+# actually, --add and --normalize can be used at the same time; +
+# the following command will convert a 512 byte block size GPT +
+# to a 4096 byte block size GPT +
+unify-gpt --block-size 4096 --add --normalize foo.img
+
+# you can use --normalize to rewrite a GPT to have a different +
+# number of partition slots +
+unify-gpt --normalize --entries 1024 foo.img
+--
+
+== See Also
+
+*parti*(1), *fdisk*(1), *parted*(1).
+
+== Links
+
+- more documentation: `/usr/share/doc/packages/parti` +
+- parti web site: https://github.com/wfeldt/parti?tab=readme-ov-file#downloads 
+
+- get latest version: 
https://software.opensuse.org/download/package?project=home:snwint:ports&package=parti
 +
+- openSUSE Build Service: https://build.opensuse.org

Reply via email to