sylware / nyangpt (public) (License: GNU AFFERO GPLv3) (since 2021-05-13) (hash sha1)
input line oriented, minimal, GPT partition creator

/nyangpt.c (740386be2905ab4c88367e3dccb318718afca674) (50694 bytes) (mode 100644) (type blob)

#ifndef NYANGPT_C
#define NYANGPT_C
/*
 * Copyright 2021 Sylvain BERTRAND <sylvain.bertrand@legeek.net>
 * LICENSE: GNU AFFERO GPLV3 
 * (NO WARANTY OF ANY KIND AND BLAHBLAHBLAH)
 */

/* quick and dirty, minimal, line input oriented uefi gpt partition creator */

/*
 * TODO:
 *  - implement ANSI terminal pagination (listing of entries and types)
 *  - utf8 to utf16 converter for names (no iconv)
 */
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdint.h>
#include <inttypes.h>
#include <endian.h>
#include <errno.h>
/*
 * how to get pertinent device information from sysfs for gpt partitioning
 *
 * for a /dev/sdX block device:
 * /sys/block/sdX/size = size of the device in blocks of 512 bytes (hardcoded)
 * /sys/block/sdX/queue/logical_block_size = size in bytes of a logical block
 *     which is the size used by lba (Logical Block Access) offsets used
 *     in gpt partitions
 * /sys/block/sdX/queue/physical_block_size = size in bytes of a physical block
 */
/*{{{ global preprocessor definitions */
#define STATIC static
#define utf8 uint8_t
#define utf16 uint16_t
#define X64_UTF8_BYTES_MAX (sizeof("18446744073709551615") - 1)
/* meh... */
#define strtou64 strtoul
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define s32 int32_t
#define u64 uint64_t
#define loop for(;;)
#define ARRAY_N(x) (sizeof(x) / sizeof(x[0]))
/*}}} global preprocessor definitions -- end */
/*{{{ nyangpt namespace */
#define blk_dev						nyangpt_blk_dev
#define build_data_from_previous_gpt			nyangpt_data_from_previous_gpt
#define change_disk_guid_prompt				nyangpt_change_disk_guid_prompt
#define colors_on					nyangpt_colors_on
#define delete_entry_prompt				nyangpt_delete_entry_prompt
#define edit_entry					nyangpt_edit_entry
#define edit_entry_attrs_prompt				nyangpt_edit_entry_attrs_prompt
#define edit_entry_first_lba_prompt			nyangpt_edit_entry_first_lba_prompt
#define edit_entry_last_lba_prompt			nyangpt_edit_entry_last_lba_prompt
#define edit_entry_prompt				nyangpt_edit_entry_prompt
#define edit_entry_to_entries				nyangpt_edit_entry_to_entries
#define edit_entry_type_prompt           		nyangpt_edit_entry_type_prompt
#define edit_entry_uniq_guid_prompt      		nyangpt_edit_entry_uniq_guid_prompt
#define entries						nyangpt_entries
#define entries_lbas_n					nyangpt_entries_lbas_n
#define entries_load					nyangpt_entries_load
#define entries_n					nyangpt_entries_n
#define entries_reset					nyangpt_entries_reset
#define entries_serialized_array			nyangpt_entries_serialized_array
#define entries_serialized_array_crc32			nyangpt_entries_serialized_array_crc32
#define entries_serialized_array_gen			nyangpt_entries_serialized_array_gen
#define entries_serialized_array_gen_entry		nyangpt_entries_serialized_array_gen_entry
#define entries_serialized_array_primary_write		nyangpt_entries_serialized_array_primary_write
#define entries_serialized_array_secondary_write	nyangpt_entries_serialized_array_secondary_write
#define entries_show					nyangpt_entries_show
#define entry_bytes_n					nyangpt_entry_bytes_n
#define entry_delete					nyangpt_entry_delete
#define entry_is_used					nyangpt_entry_is_used
#define entry_show					nyangpt_entry_show
#define entry_t						nyangpt_entry_t
#define error_pf					nyangpt_error_pf
#define first_lba_default_select			nyangpt_first_lba_default_select
#define gpt_write					nyangpt_gpt_write
#define guid_is_zero					nyangpt_guid_is_zero
#define guid_t						nyangpt_guid_t
#define guid_randomize					nyangpt_guid_randomize
#define guid_read					nyangpt_guid_read
#define guid_read_from_input				nyangph_guid_read_from_input
#define guid_write					nyangpt_guid_write
#define hdr_load					nyangpt_hdr_load
#define hdr_show					nyangpt_hdr_show
#define hdr_primary_serialized				nyangpt_hdr_primary_serialized
#define hdr_primary_serialized_gen			nyangpt_hdr_primary_serialized_gen
#define hdr_primary_serialized_write			nyangpt_hdr_primary_serialized_write
#define hdr_secondary_serialized			nyangpt_hdr_secondary_serialized
#define hdr_secondary_serialized_gen			nyangpt_hdr_secondary_serialized_gen
#define hdr_secondary_serialized_write			nyangpt_hdr_secondary_serialized_write
#define hdr_t						nyangpt_hdr_t
#define hdr_validate					nyangpt_hdr_validate
#define hdrs						nyangpt_hdrs
#define hdrs_usable_lbas_compute			nyangpt_hdrs_usable_lbas_compute
#define init_once					nyangpt_init_once
#define input_line					nyangpt_input_line
#define input_line_consume				nyangpt_input_line_consume
#define input_line_loop					nyangpt_input_line_loop
#define input_state					nyangpt_input_state
#define last_lba_default_select				nyangpt_last_lba_default_select
#define le_crc32_for_byte				nyangpt_le_crc32_for_byte
#define le_crc32_tbl					nyangpt_le_crc32_tbl
#define le_crc32_tbl_gen				nyangpt_le_crc32_tbl_gen
#define le_crc32_update					nyangpt_le_crc32_update
#define load_previous_gpt				nyangpt_load_previous_gpt
#define main_menu_prompt				nyangpt_main_menu_prompt
#define main_menu_show					nyangpt_main_menu_show
#define options_parse					nyangpt_options_parse
#define out_bold_pf					nyangpt_out_bold_pf
#define out_guid					nyangpt_out_guid
#define out_pf						nyangpt_out_pf
#define pf						nyangpt_pf
#define previous_gpt_load				nyangpt_previous_gpt_load
#define protective_mbr					nyangpt_protective_mbr
#define protective_mbr_gen				nyangpt_protective_mbr_gen
#define protective_mbr_write				nyangpt_protective_mbr_write
#define read_full					nyangpt_read_full
#define state_change_disk_guid				nyangpt_state_change_disk_guid
#define state_delete_entry				nyangpt_state_delete_entry
#define state_edit_entry				nyangpt_state_edit_entry
#define state_edit_entry_substate_attrs			nyangpt_state_edit_entry_substate_attrs
#define state_edit_entry_substate_first_lba		nyangpt_state_edit_entry_substate_first_lba
#define state_edit_entry_substate_last_lba		nyangpt_state_edit_entry_substate_last_lba
#define state_edit_entry_substate_type			nyangpt_state_edit_entry_substate_type
#define state_edit_entry_substate_uniq_quid		nyangpt_state_edit_entry_substate_uniq_quid
#define state_main_menu					nyangpt_state_main_menu
#define sysfs_infos_get					nyangpt_sysfs_infos_get
#define type_guids_lookup_name				nyangpt_type_guids_lookup_name
#define types						nyangpt_types
#define types_show					nyangpt_types_show
#define usage						nyangpt_usage
#define utf16_strdup					nyangpt_utf16_strdup
#define warning_pf					nyangpt_warning_pf
#define write_full					nyangpt_write_full
/*----------------------------------------------------------------------------*/
#define nyangpt_main		main
/*}}} nyangpt namespace -- end -----------------------------------------------*/
/*{{{ crc32b little endian */
/* stolen and cosmetized, not validated on big endian */
/* http://home.thep.lu.se/~bjorn/crc/ */
STATIC u32 le_crc32_for_byte(u32 r)
{
	u8 j;

	j = 0;
	loop  {
		if (j == 8)
			break;
		r = (r & 1 ? 0 : (u32)0xedb88320) ^ r >> 1;
		++j;
	}
	return r ^ (u32)0xff000000;
}
STATIC u32 le_crc32_tbl[0x100];
STATIC void le_crc32_tbl_gen(void)
{
	u32 i;

	i = 0;
	loop {
		if (i == 0x100)
			break;
		le_crc32_tbl[i] = le_crc32_for_byte(i);
		++i;
	}
}
STATIC void le_crc32_update(void *data, u64 bytes_n, u32* crc)
{
	u64 i;

	i = 0;
	loop {
		if (i == bytes_n)
			break;
		*crc = le_crc32_tbl[(u8)*crc ^ ((u8*)data)[i]] ^ *crc >> 8;
		++i;
	}
}
/*}}} crc32b little endian -- end *********************************************/
/*{{{ types */
struct guid_t {
	u32 blk_0;
	u16 blk_1;
	u16 blk_2;
	u16 blk_3;
	u8 blk_4[6];
};
/* serialized offsets */
#define BLK_0 0x0
#define BLK_1 0x4
#define BLK_2 0x6
#define BLK_3 0x8
#define BLK_4 0xa
/*----------------------------------------------------------------------------*/
struct hdr_t {
	bool valid;
	utf8 *str;
	u64 read_from_lba; /* _should_ be the same as the lba field */

	u8 *data;
	/* fields -- start */
	/* signature */
	/* revision */
	u32 bytes_n;
	u32 le_crc32;
	u64 lba;
	u64 alternate_hdr_lba;
	/* first usable lba */
	/* last usable lba */
	struct guid_t disk_guid;
	u64 entries_lba;
	u32 entries_n;
	u32 entry_bytes_n;
	u32 entries_le_crc32;
	/* fields -- end */

	bool entries_valid;
	u8 *entries;
};
/*----------------------------------------------------------------------------*/
struct entry_t {
	struct guid_t type;
	struct guid_t uniq;
	u64 first;
	u64 last;
	u64 attrs;
	utf16 *name;
};
#define ENTRIES_ARRAY_MIN_BYTES_N 16384 /* specs: minimum to book on the disk */
#define ENTRY_BYTES_N 0x80 /* specs */
/*}}} types -- end */
STATIC bool colors_on;
STATIC bool load_previous_gpt;
/*----------------------------------------------------------------------------*/
STATIC struct {
	utf8 *path;
	u64 sz_512_n;
	u64 logical_blk_bytes_n;
	u64 physical_blk_bytes_n;
	int fd;
	u64 last_lba;
} blk_dev;
/*----------------------------------------------------------------------------*/
STATIC u8 *protective_mbr;
/* serialized offsets */
#define BOOT_SIGNATURE_0X55	0x1fe
#define BOOT_SIGNATURE_0XAA	0x1ff
#define PART_0			0x1be
/*----------------------------------------------------------------------------*/
STATIC struct  { /* there are 2 headers, each must fit in a logical block */
	u64 first_usable_lba;
	u64 last_usable_lba;
	struct guid_t disk_guid;
	u32 bytes_n; /* the mitigated hdr size */

	struct {
		struct hdr_t primary;
		struct hdr_t secondary;
	} previous;
} hdrs;
#define HDR_SIGNATURE "EFI PART"
#define HDR_SIGNATURE_BYTES_N 8
#define HDR_REVISION 0x00010000 /* 1.0 */
#define HDR_BYTES_N 0x5c /* specs: 92 bytes */
STATIC u8 *hdr_primary_serialized;
STATIC u8 *hdr_secondary_serialized;
/*----------------------------------------------------------------------------*/
STATIC struct entry_t *entries;
STATIC u32 entries_n;
STATIC u32 entry_bytes_n;
STATIC u32 entries_lbas_n;
STATIC u8 *entries_serialized_array;
STATIC u32 entries_serialized_array_crc32;
/*----------------------------------------------------------------------------*/
STATIC utf8 *pf(utf8 *fmt,...)
{
	va_list ap;
	int r;
	utf8 *r_str;

	va_start(ap, fmt);
	r = vsnprintf(0, 0, fmt, ap);
	va_end(ap);

	r_str = malloc(r + 1); /* we want a terminating 0 */

	va_start(ap, fmt);
	vsnprintf(r_str, r + 1, fmt, ap); /* has room for the terminating 0 */
	va_end(ap);
	return r_str;
}
#define BOLD_RED if (colors_on) dprintf(1,"\x1b[38;2;255;0;0m\x1b[1m")
#define BOLD_ORANGE if (colors_on) dprintf(1,"\x1b[38;2;255;140;0m\x1b[1m")
#define BOLD if (colors_on) dprintf(1,"\x1b[1m");
#define RESTORE if (colors_on) dprintf(1, "\x1b[m")
STATIC void error_pf(utf8 *fmt,...)
{
	va_list ap;

	BOLD_RED;
	dprintf(1, "ERROR:");
	va_start(ap, fmt);
	vdprintf(1, fmt, ap);
	va_end(ap);
	RESTORE;
	exit(1);
}
STATIC void warning_pf(utf8 *fmt,...)
{
	va_list ap;

	BOLD_ORANGE;
	dprintf(1, "WARNING:");
	va_start(ap, fmt);
	vdprintf(1, fmt, ap);
	va_end(ap);
	RESTORE;
}
STATIC void out_pf(utf8 *fmt,...)
{
	va_list ap;

	va_start(ap, fmt);
	vdprintf(1, fmt, ap);
	va_end(ap);
}
STATIC void out_bold_pf(utf8 *fmt,...)
{
	va_list ap;

	BOLD;
	va_start(ap, fmt);
	vdprintf(1, fmt, ap);
	va_end(ap);
	RESTORE;
}
STATIC bool read_full(int fd, void *dest, u64 bytes_n)
{
	u64 read_bytes_n;

	read_bytes_n = 0;
	loop {
		ssize_t r;

		errno = 0;
		r = read(fd, dest + read_bytes_n,
					(size_t)(bytes_n - read_bytes_n));
		if (r == -1) {
			if (errno == EINTR)
				continue;
			return false;
		}
		read_bytes_n += (u64)r;
		if (read_bytes_n == bytes_n)
			return true;
	}
	/* unreachable */
}
STATIC bool write_full(int fd, void *src, u64 bytes_n)
{
	u64 written_bytes_n;

	/* short writes */
	written_bytes_n = 0;
	loop {
		ssize_t r;

		errno = 0;
		r = write(blk_dev.fd, src + written_bytes_n,
					(size_t)(bytes_n - written_bytes_n));
		if (r == -1) {
			if (errno == EINTR)
				continue;
			return false;
		}
		written_bytes_n += (u64)r;
		if (written_bytes_n == bytes_n)
			return true;
	}
	/* unreachable */
}
STATIC utf16 *utf16_strdup(utf16 *start)
{
	utf16 *end;
	utf16 *dst;
	size_t bytes_n;

	end = start;
	loop {
		if (*end == 0)
			break;
		++end;
	}
	++end;
	bytes_n = (size_t)((end - start) * sizeof(utf16));
	dst = calloc(1,bytes_n);
	memcpy(dst, start, bytes_n);
}
/* brain damaged mixed-endian guid */
STATIC void guid_write(void *dest, struct guid_t *src)
{
	u8 *d;
	u32 *p32;
	u16 *p16;

	d = (u8*)dest;

	p32 = (u32*)d;
	*p32 = htole32(src->blk_0); /* little endian */
	d += 4;
	p16 = (u16*)d;
	*p16 = htole16(src->blk_1); /* little endian */
	d += 2;
	p16 = (u16*)d;
	*p16 = htole16(src->blk_2); /* little endian */
	d += 2;
	p16 = (u16*)d;
	*p16 = htobe16(src->blk_3); /* big endian */
	d += 2;
	d[0] = src->blk_4[0];
	d[1] = src->blk_4[1];
	d[2] = src->blk_4[2];
	d[3] = src->blk_4[3];
	d[4] = src->blk_4[4];
	d[5] = src->blk_4[5];
}
/* brain damaged mixed-endian guid */
STATIC void guid_read(struct guid_t *dest, void *src)
{
	u32 *p32;
	u16 *p16;
	u8 *p8;

	p32 = src;
	dest->blk_0 = le32toh(*p32);
	src += 4;
	p16 = src;
	dest->blk_1 = le16toh(*p16);
	src += 2;
	p16 = src;
	dest->blk_2 = le16toh(*p16);
	src += 2;
	p16 = src;
	dest->blk_3 = be16toh(*p16);
	src += 2;
	memcpy(dest->blk_4, src, 6);
}
STATIC void sysfs_infos_get(void)
{
	int fd;
	int r;
	utf8 val_str[X64_UTF8_BYTES_MAX + 1]; /* 0 terminating char */
	utf8 *blk_dev_name;
	utf8 *sz_512_n_path;
	utf8 *logical_blk_bytes_n_path;
	utf8 *physical_blk_bytes_n_path;

	blk_dev_name = strrchr(blk_dev.path, '/');
	++blk_dev_name;
	sz_512_n_path = pf("/sys/block/%s/size", blk_dev_name);
	out_pf("%s:reading %s\n", blk_dev.path, sz_512_n_path);
	fd = open(sz_512_n_path, O_RDONLY);
	if (fd == -1)
		error_pf("%s:unable to open %s\n", blk_dev.path, sz_512_n_path);
	free(sz_512_n_path);
	/* reads are supposed to be atomic from sysfs... I guess */
	r = read(fd, val_str, sizeof(val_str));
	val_str[r - 1] = 0; /* remove the terminating '\n' */
	blk_dev.sz_512_n = strtou64(val_str, 0, 10);	
	out_bold_pf("%s:size is %"PRIu64" blocks of 512 bytes\n", blk_dev.path, blk_dev.sz_512_n);
	close(fd);

	logical_blk_bytes_n_path = pf("/sys/block/%s/queue/logical_block_size", blk_dev_name);
	out_pf("%s:reading %s\n", blk_dev.path, logical_blk_bytes_n_path);
	fd = open(logical_blk_bytes_n_path, O_RDONLY);
	if (fd == -1)
		error_pf("%s:unable to open %s\n", blk_dev.path, logical_blk_bytes_n_path);
	free(logical_blk_bytes_n_path);
	/* reads are supposed to be atomic from sysfs... I guess */
	r = read(fd, val_str, sizeof(val_str));
	val_str[r - 1] = 0; /* remove the terminating '\n' */
	blk_dev.logical_blk_bytes_n = strtou64(val_str, 0, 10);	
	out_bold_pf("%s:logical block size is %"PRIu64" bytes\n", blk_dev.path, blk_dev.logical_blk_bytes_n);
	close(fd);

	physical_blk_bytes_n_path = pf("/sys/block/%s/queue/physical_block_size", blk_dev_name);
	out_pf("%s:reading %s\n", blk_dev.path, physical_blk_bytes_n_path);
	fd = open(physical_blk_bytes_n_path, O_RDONLY);
	if (fd == -1)
		error_pf("%s:unable to open %s\n", blk_dev.path, physical_blk_bytes_n_path);
	free(physical_blk_bytes_n_path);
	/* reads are supposed to be atomic from sysfs... I guess */
	r = read(fd, val_str, sizeof(val_str));
	val_str[r - 1] = 0; /* remove the terminating '\n' */
	blk_dev.physical_blk_bytes_n = strtou64(val_str, 0, 10);	
	out_bold_pf("%s:physical block size is %"PRIu64" bytes\n", blk_dev.path, blk_dev.physical_blk_bytes_n);
	close(fd);
}
STATIC void protective_mbr_gen(void)
{
	u32 *le32_whole_dev_logical_blks_n;
	u64 whole_dev_bytes_n;
	u64 whole_dev_logical_blks_n;

	if (blk_dev.logical_blk_bytes_n < 512)
		error_pf("%s: something is wrong, the logical block size is %"PRIu64", below 512/0x200 bytes", blk_dev.path, blk_dev.logical_blk_bytes_n);
	protective_mbr = calloc(1, blk_dev.logical_blk_bytes_n);

	protective_mbr[PART_0 + 0x02] = 0x02; /* first CHS */
	protective_mbr[PART_0 + 0x04] = 0xee; /* partition type */
	protective_mbr[PART_0 + 0x05] = 0xff; /* last head */
	protective_mbr[PART_0 + 0x06] = 0xff; /* last cylinder MSBs + last sector */
	protective_mbr[PART_0 + 0x07] = 0xff; /* last cylinder LSBs */
	protective_mbr[PART_0 + 0x08] = 0x1; /* little endian */

	whole_dev_bytes_n = blk_dev.sz_512_n * 512;
	whole_dev_logical_blks_n = whole_dev_bytes_n / blk_dev.logical_blk_bytes_n;
	/* cap to max, remove the MBR in LBA 0 */
	if (whole_dev_logical_blks_n > 0x100000000)
		whole_dev_logical_blks_n = 0xffffffff;
	le32_whole_dev_logical_blks_n = (u32*)&protective_mbr[PART_0 + 0x0c];
	*le32_whole_dev_logical_blks_n = htole32( /* remove mbr */
					(u32)whole_dev_logical_blks_n - 1);

	protective_mbr[BOOT_SIGNATURE_0X55] = 0x55;
	protective_mbr[BOOT_SIGNATURE_0XAA] = 0xaa;
}
STATIC void protective_mbr_write(void)
{
	off_t r0;

	r0 = lseek(blk_dev.fd, 0, SEEK_SET);
	if (r0 != 0)
		error_pf("%s:unable to reach the start of the device\n", blk_dev.path);
	if (!write_full(blk_dev.fd, protective_mbr, 
						blk_dev.logical_blk_bytes_n))
		error_pf("%s:unable to write the protective master boot record (mbr), device mbr is now probably corrupted\n", blk_dev.path);
	out_bold_pf("%s:protective master boot record (mbr) written, %"PRIu64" bytes\n", blk_dev.path, blk_dev.logical_blk_bytes_n);
}
STATIC void entries_serialized_array_gen_entry(u32 entry_idx)
{
	u8 *entry;
	u64 *p64;

	entry = entries_serialized_array + entry_bytes_n * entry_idx;
	guid_write(&entry[0x00], &entries[entry_idx].type);
	guid_write(&entry[0x10], &entries[entry_idx].uniq);
	p64 = (u64*)&entry[0x20];
	*p64 = htole64(entries[entry_idx].first);
	p64 = (u64*)&entry[0x28];
	*p64 = htole64(entries[entry_idx].last);
}
STATIC void entries_serialized_array_gen(void)
{
	u32 entry_idx;

	entries_serialized_array = calloc(1, entries_lbas_n
						* blk_dev.logical_blk_bytes_n);
	entry_idx = 0;
	loop {
		if (entry_idx == entries_n)
			break;
		entries_serialized_array_gen_entry(entry_idx);
		++entry_idx;
	}
}
STATIC void hdr_primary_serialized_gen(void)
{
	u64 *p64;
	u32 *p32;
	u16 *p16;
	u32 le_crc32;

	hdr_primary_serialized = calloc(1, blk_dev.logical_blk_bytes_n);

	memcpy(hdr_primary_serialized, HDR_SIGNATURE, 8);

	p32 = (u32*)&hdr_primary_serialized[0x08];
	*p32 = htole32(HDR_REVISION);

	p32 =(u32*)&hdr_primary_serialized[0x0c];
	*p32 = htole32(hdrs.bytes_n);

	/* the CRC32 will go there, 0 for its calculation */

	p64 = (u64*)&hdr_primary_serialized[0x18];
	*p64 = htole64(0x00000001); /* lba of this hdr */

	p64 = (u64*)&hdr_primary_serialized[0x20];
	*p64 = htole64(blk_dev.last_lba); /* the hdr at the end */

	p64 = (u64*)&hdr_primary_serialized[0x28];
	*p64 = htole64(hdrs.first_usable_lba);

	p64 = (u64*)&hdr_primary_serialized[0x30];
	*p64 = htole64(hdrs.last_usable_lba);

	guid_write(&hdr_primary_serialized[0x38], &hdrs.disk_guid);

	p64 = (u64*)&hdr_primary_serialized[0x48];
	*p64 = htole64(2); /* skip mbr and hdr */

	p32 = (u32*)&hdr_primary_serialized[0x50];
	*p32 = htole32(entries_n);

	p32 = (u32*)&hdr_primary_serialized[0x54];
	*p32 = htole32(entry_bytes_n);

	p32 = (u32*)&hdr_primary_serialized[0x58];
	*p32 = htole32(entries_serialized_array_crc32);

	/* crc32 on exactly the header size */
	le_crc32 = 0;
	le_crc32_update(hdr_primary_serialized, hdrs.bytes_n, &le_crc32);
	p32 = (u32*)&hdr_primary_serialized[0x10];
	*p32 = le_crc32;
}
STATIC void hdr_secondary_serialized_gen(void)
{
	u64 *p64;
	u32 *p32;
	u16 *p16;
	u32 le_crc32;

	hdr_secondary_serialized = calloc(1, blk_dev.logical_blk_bytes_n);

	memcpy(hdr_secondary_serialized, HDR_SIGNATURE, 8);

	p32 = (u32*)&hdr_secondary_serialized[0x08];
	*p32 = htole32(HDR_REVISION);

	p32 =(u32*)&hdr_secondary_serialized[0x0c];
	*p32 = htole32(hdrs.bytes_n);

	/* the CRC32 will go there, 0 for its calculation */

	p64 = (u64*)&hdr_secondary_serialized[0x18];
	*p64 = htole64(blk_dev.last_lba); /* this hdr */

	p64 = (u64*)&hdr_secondary_serialized[0x20];
	*p64 = htole64(1); /* the hdr at the beginning */

	p64 = (u64*)&hdr_secondary_serialized[0x28];
	*p64 = htole64(hdrs.first_usable_lba);

	p64 = (u64*)&hdr_secondary_serialized[0x30];
	*p64 = htole64(hdrs.last_usable_lba);

	guid_write(&hdr_secondary_serialized[0x38], &hdrs.disk_guid);

	p64 = (u64*)&hdr_secondary_serialized[0x48];
	*p64 = htole64(blk_dev.last_lba - entries_lbas_n);

	p32 = (u32*)&hdr_secondary_serialized[0x50];
	*p32 = htole32(entries_n);

	p32 = (u32*)&hdr_secondary_serialized[0x54];
	*p32 = htole32(entry_bytes_n); 

	p32 = (u32*)&hdr_secondary_serialized[0x58];
	*p32 = htole32(entries_serialized_array_crc32);

	/* crc32 on exactly the header size */
	le_crc32 = 0;
	le_crc32_update(hdr_secondary_serialized, hdrs.bytes_n, &le_crc32);
	p32 = (u32*)&hdr_secondary_serialized[0x10];
	*p32 = le_crc32;
}
STATIC void hdr_primary_serialized_write(void)
{
	off_t r;
	off_t start;
	u64 bytes_to_write_n;

	/* skip the mbr */
	start = (off_t)blk_dev.logical_blk_bytes_n;
	r = lseek(blk_dev.fd, start, SEEK_SET);
	if (r != start)
		error_pf("%s:unable to reach the first lba of the device\n", blk_dev.path);
	bytes_to_write_n = (size_t)(blk_dev.logical_blk_bytes_n);
	if (!write_full(blk_dev.fd, hdr_primary_serialized, bytes_to_write_n))
		error_pf("%s:unable to write the primary GPT header, block device is now probably corrupted\n", blk_dev.path);
	out_bold_pf("%s:primary GPT header written, %"PRIu64" bytes\n", blk_dev.path, bytes_to_write_n);
}
STATIC void hdr_secondary_serialized_write(void)
{
	off_t r;
	off_t start;
	u64 bytes_to_write_n;

	start = (off_t)(blk_dev.last_lba * blk_dev.logical_blk_bytes_n);
	r = lseek(blk_dev.fd, start, SEEK_SET);
	if (r != start)
		error_pf("%s:unable to reach the lba of the secondary header\n", blk_dev.path);
	bytes_to_write_n = blk_dev.logical_blk_bytes_n;
	if (!write_full(blk_dev.fd, hdr_secondary_serialized, bytes_to_write_n))
		error_pf("%s:unable to write the secondary GPT header, block device is now probably corrupted\n", blk_dev.path);
	out_bold_pf("%s:secondary GPT header written, %"PRIu64" bytes\n", blk_dev.path, bytes_to_write_n);
}
STATIC void entries_serialized_array_primary_write(void)
{
	off_t r;
	off_t start;
	u64 bytes_to_write_n;

	/* skip the mbr and the primary hdr */
	start = (off_t)(blk_dev.logical_blk_bytes_n * 2);
	r = lseek(blk_dev.fd, start, SEEK_SET);
	if (r != start)
		error_pf("%s:unable to reach the first lba for the primary entries\n", blk_dev.path);
	bytes_to_write_n = blk_dev.logical_blk_bytes_n * entries_lbas_n;
	if (!write_full(blk_dev.fd, entries_serialized_array, bytes_to_write_n))
		error_pf("%s:unable to write the primary entries, block device is now probably corrupted\n", blk_dev.path);
	out_bold_pf("%s:primary entries written, %"PRIu64" bytes\n", blk_dev.path, bytes_to_write_n);
}
STATIC void entries_serialized_array_secondary_write(void)
{
	off_t r;
	off_t start;
	u64 bytes_to_write_n;

	/* skip the secondary hdr, offset arithmetic */
	start = (off_t)(blk_dev.logical_blk_bytes_n * (blk_dev.last_lba
							- entries_lbas_n));
	r = lseek(blk_dev.fd, start, SEEK_SET);
	if (r != start)
		error_pf("%s:unable to reach the first lba for the secondary entries\n", blk_dev.path);
	bytes_to_write_n = blk_dev.logical_blk_bytes_n * entries_lbas_n;
	if (!write_full(blk_dev.fd, entries_serialized_array, bytes_to_write_n))
		error_pf("%s:unable to write the secondary entries, block device is now probably corrupted\n", blk_dev.path);
	out_bold_pf("%s:secondary entries written, %"PRIu64" bytes\n", blk_dev.path, bytes_to_write_n);
}
STATIC void hdr_load(struct hdr_t *hdr)
{
	off_t r;
	off_t lba_off;

	lba_off = (off_t)(blk_dev.logical_blk_bytes_n * hdr->read_from_lba);
	
	hdr->data = calloc(1, blk_dev.logical_blk_bytes_n);
	r = lseek(blk_dev.fd, lba_off, SEEK_SET);
	if (r != lba_off) {
		warning_pf("%s:unable to seek the device to the %s GPT header\n", blk_dev.path, hdr->str);
		return;
	}
	if (!read_full(blk_dev.fd, hdr->data, blk_dev.logical_blk_bytes_n)) {
		warning_pf("%s:unable to load the %s GPT header logical block\n", blk_dev.path, hdr->str);
		free(hdr->data);
		hdr->data = 0;
	}
}
STATIC void hdr_validate(struct hdr_t *hdr)
{
	u32 *p32;
	u32 le_crc32;
	u64 *p64;

	if (hdr->data == 0)
		return;
	if (memcmp(hdr->data, HDR_SIGNATURE, HDR_SIGNATURE_BYTES_N) != 0) {
		warning_pf("%s:the %s GPT header has the wrong signature\n", blk_dev.path, hdr->str);
		goto free_data;
	} 
	out_bold_pf("%s:found, %s, GPT header\n", blk_dev.path, hdr->str);
	p32 = (u32*)&hdr->data[0x10];
	hdr->le_crc32 = *p32;
	/* must 0 the crc32 to perform our crc32 */
	*p32 = 0;
	p32 = (u32*)&hdr->data[0x0c];
	hdr->bytes_n = le32toh(*p32);
	le_crc32 = 0;
	le_crc32_update(hdr->data, hdr->bytes_n, &le_crc32);
	if (le_crc32 != hdr->le_crc32) {
		warning_pf("%s:the %s GPT header has a bad crc32\n", blk_dev.path, hdr->str);
		goto free_data;
	}
	p64 = (u64*)&hdr->data[0x18];
	hdr->lba = le64toh(*p64);
	p64 = (u64*)&hdr->data[0x20];
	hdr->alternate_hdr_lba = le64toh(*p64);
	guid_read(&hdr->disk_guid, &hdr->data[0x38]);
	p64 = (u64*)&hdr->data[0x48];
	hdr->entries_lba = le64toh(*p64);
	p32 = (u32*)&hdr->data[0x50];
	hdr->entries_n = le32toh(*p32);
	p32 = (u32*)&hdr->data[0x54];
	hdr->entry_bytes_n = le32toh(*p32);
	p32 = (u32*)&hdr->data[0x58];
	hdr->entries_le_crc32 = *p32;

	hdr->valid = true;
	return;
free_data:
	free(hdr->data);
	hdr->data = 0;
}
STATIC void entries_load(struct hdr_t *hdr)
{
	off_t r;
	off_t entries_off;
	u32 hdr_entries_bytes_n;
	u32 le_crc32;

	entries_off = hdr->entries_lba * blk_dev.logical_blk_bytes_n;
	r = lseek(blk_dev.fd, entries_off, SEEK_SET);
	if (r != entries_off) {
		warning_pf("%s:unable to seek the device to the, %s, GPT header\n", blk_dev.path, hdr->str);
		return;
	}
	hdr_entries_bytes_n = hdr->entry_bytes_n * hdr->entries_n;
	hdr->entries = calloc(1, (size_t)(hdr_entries_bytes_n));
	if(!read_full(blk_dev.fd, hdr->entries, hdr_entries_bytes_n)) {
		warning_pf("%s:unable to read the, %s, entries\n", blk_dev.path, hdr->str);
		goto free_entries;
	}
	le_crc32 = 0;	
	le_crc32_update(hdr->entries, hdr_entries_bytes_n, &le_crc32);
	if (le_crc32 != hdr->entries_le_crc32) {
		warning_pf("%s:%s entries have a bad crc32\n", blk_dev.path, hdr->str);
		goto free_entries;
	}
	hdr->entries_valid = true;
	return;
free_entries:
	free(hdr->entries);
}
STATIC void build_data_from_previous_gpt(void)
{
	struct hdr_t *hdr;
	u32 entry_idx;
	u32 entries_bytes_n;

	/* first, lookup for valid data */
	if (hdrs.previous.primary.valid && hdrs.previous.primary.entries_valid)
		hdr = &hdrs.previous.primary;
	else if (hdrs.previous.secondary.valid
				&& hdrs.previous.secondary.entries_valid)
		hdr = &hdrs.previous.secondary;
	else
		return; /* no valid data to build from */
	/* header */
	memcpy(&hdrs.disk_guid, &hdr->disk_guid, sizeof(hdrs.disk_guid));
	hdrs.bytes_n = hdr->bytes_n;
	/* entries */
	entries = calloc(hdr->entries_n, hdr->entry_bytes_n);
	entries_n = hdr->entries_n;
	entry_bytes_n = hdr->entry_bytes_n;
	entries_bytes_n = hdr->entries_n * hdr->entry_bytes_n;
	entries_lbas_n = entries_bytes_n / blk_dev.logical_blk_bytes_n
			+ ((entries_bytes_n % blk_dev.logical_blk_bytes_n) == 0
								? 0 : 1);
	entry_idx = 0;
	loop {
		u8 *src_entry;
		struct entry_t *dst_entry;
		u64 *p64;

		if (entry_idx == entries_n)
			break;
		src_entry = hdr->entries + entry_idx * hdr->entry_bytes_n;
		dst_entry = &entries[entry_idx];

		guid_read(&dst_entry->type, src_entry);
		guid_read(&dst_entry->uniq, &src_entry[0x10]);
		p64 = (u64*)&src_entry[0x20];
		dst_entry->first = le64toh(*p64);
		p64 = (u64*)&src_entry[0x28];
		dst_entry->last = le64toh(*p64);
		p64 = (u64*)&src_entry[0x30];
		dst_entry->attrs = le64toh(*p64);
		dst_entry->name = utf16_strdup((utf16*)&src_entry[0x38]);
		++entry_idx;
	}
}
STATIC void previous_gpt_load(void)
{
	hdrs.previous.primary.read_from_lba = 1;
	hdrs.previous.primary.str = "primary";
	hdr_load(&hdrs.previous.primary);
	hdr_validate(&hdrs.previous.primary);
	if (hdrs.previous.primary.valid) {
		entries_load(&hdrs.previous.primary);
		if (hdrs.previous.primary.entries_valid)
			return;
		/* damn! we have a valid header but with invalid entries */
		hdrs.previous.secondary.read_from_lba =
				hdrs.previous.primary.alternate_hdr_lba;
		hdrs.previous.secondary.str = "alternate from primary header";
	} else { /* arg! try the device last lba */
		hdrs.previous.secondary.read_from_lba = blk_dev.last_lba;
		hdrs.previous.secondary.str = "from device last lba";
	}
	/*
	 * here, something went wrong with the primary header. 2 cases:
	 *  - the primary header is ok but the entries are not, then we are
	 *    trying the alternate header from the primary header data.
	 *  - the primary header is nok, we are try to look for a secondary
	 *    header from the last lba of the device.
	 */
	hdr_load(&hdrs.previous.secondary);
	hdr_validate(&hdrs.previous.secondary);
	if (hdrs.previous.secondary.valid)
		entries_load(&hdrs.previous.secondary);
}
STATIC void hdrs_usable_lbas_compute(void)
{
	/* protective mbr, hdr, entries */
	hdrs.first_usable_lba = 0 + 2 + entries_lbas_n;
	/* hdr, entries */
	hdrs.last_usable_lba = blk_dev.last_lba - 1 - entries_lbas_n;
}
STATIC void entries_reset(void)
{
	u32 i;
	utf16 zero;
	u32 entries_bytes_n;

	if (entries_n != 0) {
		i = 0;
		loop { /* the utf16 names */
			if (i == entries_n)
				break;
			free(entries[i].name);
			++i;
		}
		free(entries);
	}
	/* actually 128 entries */
	entries_n = ENTRIES_ARRAY_MIN_BYTES_N / ENTRY_BYTES_N;
	entries = calloc(entries_n, ENTRY_BYTES_N);
	entry_bytes_n = ENTRY_BYTES_N;

	zero = 0;	
	i = 0;
	loop {
		if (i == entries_n)
			break;
		entries[i].name = utf16_strdup(&zero);
		++i;
	}
	entries_bytes_n = entries_n * entry_bytes_n;	
	entries_lbas_n = entries_bytes_n / blk_dev.logical_blk_bytes_n
		+ (entries_bytes_n % entry_bytes_n == 0 ? 0 : 1);
}
/******************************************************************************/
STATIC void out_guid(struct guid_t *guid)
{
	u8 i;
	out_pf("%08x-%04x-%04x-%04x-", guid->blk_0, guid->blk_1, guid->blk_2,
					guid->blk_3);
	i = 0;
	loop {
		if (i == 6)
			break;
		out_pf("%02x", guid->blk_4[i]);
		++i;
	}
}
struct {
	struct guid_t  guid;
	utf8 *name;
} types[] = {
	[0].guid.blk_0 = 0xc12a7328,
	[0].guid.blk_1 = 0xf81f,
	[0].guid.blk_2 = 0x11d2,
	[0].guid.blk_3 = 0xba4b,
	[0].guid.blk_4 = {0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b},
	[0].name = "EFI system",

	[1].guid.blk_0 = 0x4f68bce3,
	[1].guid.blk_1 = 0xe8cd,
	[1].guid.blk_2 = 0x4db1,
	[1].guid.blk_3 =  0x96e7,
	[1].guid.blk_4 = {0xfb, 0xca, 0xf9, 0x84, 0xb7, 0x09},
	[1].name = "linux root x86_64",

	[2].guid.blk_0 = 0x0657fd6d,
	[2].guid.blk_1 = 0xa4ab,
	[2].guid.blk_2 = 0x43c4,
	[2].guid.blk_3 = 0x84e5,
	[2].guid.blk_4 = {0x09, 0x33, 0xc8, 0x4b, 0x4f, 0x4f},
	[2].name = "linux swap",
};
STATIC void types_show(void)
{
	u32 i;
	u32 n;

	n = ARRAY_N(types);
	i = 0;
	loop {
		if (i == n)
			break;
		out_pf("%"PRIu32": ", i + 1);
		out_guid(&types[i].guid);
		out_pf(" %s\n", types[i].name);
		++i;
	}
}
STATIC utf8 *type_guids_lookup_name(struct guid_t *guid)
{
	u32 i;
	u32 n;

	n = ARRAY_N(types);
	i = 0;
	loop {
		if (i == n)
			break;
		if (memcmp(guid, &types[i].guid, sizeof(*guid)) == 0)
			return types[i].name;
		++i;
	}
	return "unknown";
}
STATIC void entry_show(u32 idx, struct entry_t *entry)
{
	utf16 *pc;
	utf8 *type_str;

	out_pf("%"PRIu32":    %"PRIu64"    %"PRIu64"    ", idx + 1, entry->first, entry->last);
	out_guid(&entry->type);
	type_str = type_guids_lookup_name(&entry->type);
	out_pf("(%s)", type_str);
	out_pf("\n    ");
	out_guid(&entry->uniq);
	out_pf("    ");
	out_pf("0x%"PRIx64"    \"", entry->attrs);
	pc = entry->name;
	loop {
		u16 c;

		c = le16toh(*pc);
		if (c == 0)
			break;
		/* keep it simple */
		if ((u16)'0' <=  c  && c <= (u16)'9'
			&& (u16)'A' <= c && c <= (u16)'Z'
			&& (u16)'a' <= c && c <= (u16)'z')
			out_pf("%c", c);
		else
			out_pf("�");
		++pc;
	}
	out_pf("\"\n");
}
STATIC bool guid_is_zero(struct guid_t *guid)
{
	u8 *b;
	u8 *last;

	b = (u8*)guid;
	last = b + sizeof(*guid) - 1;
	loop {
		if (*b != 0)
			return false;
		if (b == last)
			return true;
		++b;
	}
}
STATIC bool entry_is_used(u32 idx)
{
	struct entry_t *entry;

	entry = &entries[idx];
	if (	   guid_is_zero(&entry->type)
		&& guid_is_zero(&entry->uniq)
		&& entry->first == 0
		&& entry->last == 0
		&& entry->attrs == 0
		&& entry->name[0] == 0) {
		return false;
	}
	return true;
}
STATIC void entry_delete(u32 idx)
{
	struct entry_t *entry;
	utf16 empty_str;

	empty_str = 0;
	entry = &entries[idx];
	free(entry->name);
	memset(entry, 0, sizeof(*entry));
	entry->name = utf16_strdup(&empty_str);
}
STATIC void entries_show(void)
{
	u32 entry_idx;

	if (entries_n == 0) {
		out_pf("no partition entries\n");
		return;
	} else {
		out_bold_pf("array of partition entries has %"PRIu32" slots:\n", entries_n);
	}
	entry_idx = 0;
	out_bold_pf("slot / first / last / type(guid) \n");
	out_bold_pf("    uniq_guid / attributes / name\n");
	loop {
		if (entry_idx == entries_n)
			break;
		if (entry_is_used(entry_idx))
			entry_show(entry_idx, &entries[entry_idx]);
		++entry_idx;
	}
}
STATIC void main_menu_show(void)
{
	out_pf("\
Command / Action\n\
p    print the partition entries\n\
n    new partition entry\n\
d    delete a partition entry\n\
h    print the header\n\
u    change disk guid\n\
w    write and quit\n\
q    quit without saving\n");
}
STATIC u8 input_line[BUFSIZ];
STATIC void *(*input_state)(size_t lf);
/* list of states -- start */
STATIC void *state_main_menu(size_t lf);
STATIC void *state_delete_entry(size_t lf);
STATIC void *state_change_disk_guid(size_t lf);
STATIC void *state_edit_entry(size_t lf);
STATIC void *state_edit_entry_substate_first_lba(size_t lf);
STATIC void *state_edit_entry_substate_last_lba(size_t lf);
STATIC void *state_edit_entry_substate_type(size_t lf);
STATIC void *state_edit_entry_substate_uniq_quid(size_t lf);
STATIC void *state_edit_entry_substate_attrs(size_t lf);
/* list of states -- end */
STATIC struct {
	u32 num;
	u64 first_lba_default;
	u64 first_lba;
	u64 last_lba_default;
	u64 last_lba;
	u32 type;
	struct guid_t uniq;
	u64 attrs;
} edit_entry;
/*{{{ prompts */
STATIC void edit_entry_attrs_prompt(void)
{
	out_pf("attributes as an hexadecimal number (default to 0x0): ");
}
STATIC void edit_entry_uniq_guid_prompt(void)
{
	out_pf("uniq guid (r to randomize): ");
}
STATIC void edit_entry_type_prompt(void)
{
	out_pf("number of the type of partition (l for the list): ");
}
STATIC void main_menu_prompt(void)
{
	out_pf("command (m for help): ");
}
STATIC void delete_entry_prompt(void)
{
	out_pf("number of the partition entry to delete: ");
}
STATIC void edit_entry_last_lba_prompt(void)
{
	out_pf("last logical block lba (default to %"PRIu64"): ",
						edit_entry.last_lba_default);
}
STATIC void edit_entry_prompt(void)
{
	out_pf("partition entry number (1-%"PRIu32"): ", entries_n);
}
STATIC void edit_entry_first_lba_prompt(void)
{
	out_pf("first logical block lba (default to %"PRIu64"): ",
						edit_entry.first_lba_default); 
}
STATIC void change_disk_guid_prompt(void)
{
	out_pf("current disk guid is ");
	out_guid(&hdrs.disk_guid);
	out_pf("\nnew one (r to randomize): ");	
}
/*}}} prompts -- end */
STATIC void first_lba_default_select(void)
{
	u32 idx;

	/* mbr, header, entries. Should not need to cap this */
	edit_entry.first_lba_default = hdrs.first_usable_lba;
	if (edit_entry.num == 1)
		return; 
	/* num >= 2, idx >= 1 */	
	idx = edit_entry.num - 2;
	/*
	 * we are going "backward" to locate the right before used entry, then
	 * we will use the last lba from this entry to compute the first
	 * lba for this one
	 */
	loop {
		if (entry_is_used(idx)) {
			edit_entry.first_lba_default = entries[idx].last + 1;
			/* hard capping */
			if (edit_entry.first_lba_default < hdrs.first_usable_lba)
				edit_entry.first_lba_default = hdrs.first_usable_lba;
			if (edit_entry.first_lba_default > hdrs.last_usable_lba)
				edit_entry.first_lba_default = hdrs.last_usable_lba;
			break;
		}
		if (idx == 0)
			break;
		idx--;
	}
}
STATIC void last_lba_default_select(void)
{
	u32 idx;

	edit_entry.last_lba_default = hdrs.last_usable_lba;
	if (edit_entry.num == entries_n)
		return; 
	/* num <= entries_n - 1, idx <= entries_n - 2 */	
	idx = edit_entry.num;
	/*
	 * we are going forward to reach the first used entry and then use
	 * the lba right before its first lba.
	 */
	loop {
		if (idx == entries_n)
			break;
		if (entry_is_used(idx)) {
			edit_entry.last_lba_default = entries[idx].first - 1;
			/* hard capping */
			if (edit_entry.last_lba_default < hdrs.first_usable_lba)
				edit_entry.last_lba_default = hdrs.first_usable_lba;
			if (edit_entry.last_lba_default > hdrs.last_usable_lba)
				edit_entry.last_lba_default = hdrs.last_usable_lba;
			break;
		}
		++idx;
	}
}
STATIC void *hdr_show(void)
{
	out_pf("header:\ndisk guid ");
	out_guid(&hdrs.disk_guid);
	out_pf("\nfirst usable lba %"PRIu64"\n", hdrs.first_usable_lba);
	out_pf("last usable lba %"PRIu64"\n", hdrs.last_usable_lba);
	out_pf("%s last lba is %"PRIu64"\n", blk_dev.path, blk_dev.last_lba);
}
STATIC void guid_randomize(struct guid_t *guid)
{
	int fd;
	
	fd = open("/dev/urandom", O_RDONLY);
	if (fd == -1) {
		warning_pf("unable to open /dev/urandom for guid randomization\n");
		return;
	}
	if (!read_full(fd, guid, sizeof(*guid)))
		warning_pf("something went wrong while reading from /dev/urandom, the guid is probably corrupted\n");
	close(fd);
}
/* best effort */
STATIC void guid_read_from_input(struct guid_t *guid, size_t lf)
{
	u8 *blk_start;
	u8 *blk_end;
	u8 save;
	u8 *end;

	input_line[lf] = 0;
	end = input_line + lf;

	blk_start = input_line;
	blk_end =  strchr(blk_start, '-');
	if (blk_end == 0)
		return;
	*blk_end = 0;
	guid->blk_0 = (u32)strtou64(blk_start, 0, 16);
	blk_start = blk_end + 1;
	if (blk_start >= end)
		return;
	blk_end = strchr(blk_start, '-');
	if (blk_end == 0)
		return;
	*blk_end = 0;
	guid->blk_1 = (u16)strtou64(blk_start, 0, 16);
	blk_start = blk_end + 1;
	if (blk_start >= end)
		return;
	blk_end = strchr(blk_start, '-');
	if (blk_end == 0)
		return;
	*blk_end = 0;
	guid->blk_2 = (u16)strtou64(blk_start, 0, 16);
	blk_start = blk_end + 1;
	if (blk_start >= end)
		return;
	blk_end = strchr(blk_start, '-');
	if (blk_end == 0)
		return;
	*blk_end = 0;
	guid->blk_3 = (u16)strtou64(blk_start, 0, 16);
	/* blk_4 */
	blk_start = blk_end + 1;
	if (blk_start >= end)
		return;
	blk_end = blk_start + 12; /* 6 bytes */
	if (blk_end > end)
		return;
	save = blk_start[2];
	blk_start[2] = 0;
	guid->blk_4[0] = (u8)strtou64(&blk_start[0], 0, 16);
	blk_start[2] = save;

	save = blk_start[4];
	blk_start[4] = 0;
	guid->blk_4[1] = (u8)strtou64(&blk_start[2], 0, 16);
	blk_start[4] = save;

	save = blk_start[6];
	blk_start[6] = 0;
	guid->blk_4[2] = (u8)strtou64(&blk_start[4], 0, 16);
	blk_start[6] = save;

	save = blk_start[8];
	blk_start[8] = 0;
	guid->blk_4[3] = (u8)strtou64(&blk_start[6], 0, 16);
	blk_start[8] = save;

	save = blk_start[10];
	blk_start[10] = 0;
	guid->blk_4[4] = (u8)strtou64(&blk_start[8], 0, 16);
	blk_start[10] = save;

	guid->blk_4[5] = (u8)strtou64(&blk_start[10], 0, 16);
}
STATIC void *edit_entry_to_entries(void)
{
	struct entry_t *dst_entry;

	dst_entry = &entries[edit_entry.num - 1];
	memcpy(&dst_entry->type, &types[edit_entry.type - 1].guid,
						sizeof(dst_entry->type));
	memcpy(&dst_entry->uniq, &edit_entry.uniq, sizeof(dst_entry->uniq));
	dst_entry->first = edit_entry.first_lba;
	dst_entry->last = edit_entry.last_lba;
	dst_entry->attrs = edit_entry.attrs;
	/* don't support name input yet */
}
STATIC void gpt_write(void)
{
	entries_serialized_array_gen();
	/* crc32 on exactly the entries array size, not lba bounded */
	entries_serialized_array_crc32 = 0;
	le_crc32_update(entries_serialized_array, entry_bytes_n * entries_n,
					&entries_serialized_array_crc32);
	hdr_primary_serialized_gen();
	hdr_secondary_serialized_gen();
	protective_mbr_gen();
	protective_mbr_write();
	hdr_primary_serialized_write();
	hdr_secondary_serialized_write();
	entries_serialized_array_primary_write();
	entries_serialized_array_secondary_write();
}
/*{{{ states */
STATIC void *state_edit_entry_substate_attrs(size_t lf)
{
	if (lf == 0) {
		edit_entry.attrs = 0;
		goto exit;
	}
	input_line[lf] = 0;
	edit_entry.attrs = strtou64(input_line, 0, 16);
exit:
	/*
	 * TODO: don't want to use iconv, will implement an utf8 to utf16
	 * converter another time.
 	 */
	edit_entry_to_entries();
	main_menu_prompt();
	return state_main_menu;
}
STATIC void *state_edit_entry_substate_uniq_quid(size_t lf)
{
	if (lf == 1 && input_line[0] == 'r') {
		guid_randomize(&edit_entry.uniq);
		out_pf("generated guid is : ");
		goto exit;
	}
	guid_read_from_input(&edit_entry.uniq, lf);
	out_pf("read guid is : ");
exit:
	out_guid(&edit_entry.uniq);
	out_pf("\n");
	edit_entry_attrs_prompt();
	return state_edit_entry_substate_attrs;
}
STATIC void *state_edit_entry_substate_type(size_t lf)
{
	if (lf == 1 && input_line[0] == 'l') {
		types_show();
		edit_entry_type_prompt();
		return state_edit_entry_substate_type;
	}
	input_line[lf] = 0;
	edit_entry.type = (u32)strtou64(input_line, 0, 10);
	if (edit_entry.type == 0 || edit_entry.type > ARRAY_N(types)) {
		warning_pf("invalid type number\n");
		edit_entry_type_prompt();
		return state_edit_entry_substate_type;
	}
	edit_entry_uniq_guid_prompt();
	return state_edit_entry_substate_uniq_quid;
}
STATIC void *state_edit_entry_substate_last_lba(size_t lf)
{
	if (lf == 0)
		edit_entry.last_lba = edit_entry.last_lba_default;
	else {
		input_line[lf] = 0;
		edit_entry.last_lba = strtou64(input_line, 0, 10);
	}
	edit_entry_type_prompt();
	return state_edit_entry_substate_type;
}
STATIC void *state_edit_entry_substate_first_lba(size_t lf)
{
	if (lf == 0) {
		edit_entry.first_lba = edit_entry.first_lba_default;
	} else {
		input_line[lf] = 0;
		edit_entry.first_lba = strtou64(input_line, 0, 10);
	}
	last_lba_default_select();
	edit_entry_last_lba_prompt();
	return state_edit_entry_substate_last_lba;
}
STATIC void *state_edit_entry(size_t lf)
{
	input_line[lf] = 0;
	edit_entry.num = (u32)strtou64(input_line, 0, 10);
	if (1 <= edit_entry.num && edit_entry.num <= entries_n) {
		first_lba_default_select();
		edit_entry_first_lba_prompt();
		return state_edit_entry_substate_first_lba;
	} else
		warning_pf("%"PRIu32" is an invalid partition entry number\n", edit_entry.num);
	main_menu_prompt();
	return state_main_menu;
}
STATIC void *state_delete_entry(size_t lf)
{
	u8 *end;
	u32 entry_num;

	input_line[lf] = 0;
	entry_num = (u32)strtou64(input_line, 0, 10);
	if (1 <= entry_num && entry_num <= entries_n) {
		out_bold_pf("deleting partition entry %"PRIu64"\n", entry_num);
		entry_delete((u32)(entry_num - 1));
	} else 
		out_bold_pf("partition number out of range %"PRIu64"\n", entry_num);
	main_menu_prompt();
	return state_main_menu;
}
STATIC void *state_change_disk_guid(size_t lf)
{
	if (lf == 1) {
		if (input_line[0] == 'r') {
			guid_randomize(&hdrs.disk_guid);
			out_pf("randomized disk guid is ");
		}
	} else {
		guid_read_from_input(&hdrs.disk_guid, lf);
		out_pf("read disk guid is ");
	}
	out_guid(&hdrs.disk_guid);
	out_pf("\n");
	main_menu_prompt();
	return state_main_menu;
}
STATIC void *state_main_menu(size_t lf)
{
	if (lf == 1) {
		switch(input_line[0]) {
		case 'q':
			out_pf("quit\n");
			exit(0);
		case 'm':
			main_menu_show();
			break;
		case 'p':
			entries_show();
			break;
		case 'h':
			hdr_show();
			break;
		case 'd':
			delete_entry_prompt();
			return state_delete_entry;
		case 'n':
			edit_entry_prompt();
			return state_edit_entry;
		case 'u':
			change_disk_guid_prompt();
			return state_change_disk_guid;
		case 'w':
			gpt_write();
			exit(0);
		default:
			break;
		}
	}
	main_menu_prompt();
	return state_main_menu;
}
/*}}} states -- end */
/* end is the index of the byte past the last read byte */
STATIC size_t input_line_consume(size_t lf, size_t end)
{
	input_state = input_state(lf);

	/* update the input line buffer and offsets */
	if (lf == (BUFSIZ - 1)) { /* lf is the last char in our line buffer */
		memset(input_line, 0, BUFSIZ);
		return 0;
	}	
	memmove(input_line, input_line + lf + 1, end - (lf + 1));
	return end - (lf + 1);
}
STATIC void input_line_loop(void)
{
	bool discarding;
	size_t end;

	end = 0;
	memset(input_line, 0, BUFSIZ);
	discarding = false;
	input_state = state_main_menu;
	main_menu_prompt();
	loop {
		int r;
		size_t lf;

		errno = 0;
		r = read(0, &input_line[end], BUFSIZ - end);
		if (r == -1) {
			if (errno == EINTR)
				continue;
			error_pf("error reading input line from standard input\n");
		}
		lf = end;
		end += (size_t)r;
		loop { /* scan what was read for lf */
			if (lf == end)
				break;
			if (input_line[lf] == '\n') {
				if (discarding) {
					discarding = false;
					end = 0;
					break;
				}
				end = input_line_consume(lf, end);
				break;
			}
			++lf;
		}
		if (end == BUFSIZ) {
			if (!discarding) {
				out_pf("your input line is too long, discarding...\n");
				discarding = true;
			}
			end = 0;
		}
	}
}
STATIC void init_once(void)
{
	le_crc32_tbl_gen();
	colors_on = true;
	load_previous_gpt = true;
	entries = 0;
	entries_n = 0;
	memset(&hdrs, 0, sizeof(hdrs));
}
STATIC void usage(void)
{
	BOLD;
	out_pf("\
nyangpt: line input oriented minimal GPT partition creator\n\
\n\
usage:\n\
    nyangpt [-nc] [-nl] [-h|--help][ [--] device_path\n\
\n\
        -nc: disable ANSI terminal colors\n\
	-nl: don't load previous GPT partitions\n\
        -h|--help: this summary\n");
	RESTORE;
	exit(0);
}
STATIC void options_parse(u32 argc, utf8 **argv)
{
	u32 arg;
	bool no_more_options;
	bool print_usage;

	if (argc == 1)
		usage();
	arg = 1;
	no_more_options = false;
	print_usage = false;
	blk_dev.path = 0;
	loop {
		if (arg == argc)
			break;
		if (no_more_options) {
			if (blk_dev.path == 0)
				blk_dev.path = argv[arg];
		} else if (strcmp(argv[arg], "--") == 0) {
			no_more_options = true;
		} else if (strcmp(argv[arg], "-nc") == 0) {
			colors_on = false;
		} else if (strcmp(argv[arg], "-nl") == 0) {
			load_previous_gpt = false;
		} else if (strcmp(argv[arg], "-h") == 0
					|| strcmp(argv[arg], "--help") == 0) {
			print_usage = true;
		} else {
			if (blk_dev.path == 0)
				blk_dev.path = argv[arg];
		}
		++arg;
	}
	if (print_usage || blk_dev.path == 0)
		usage();
}
int nyangpt_main(int argc, utf8 **argv)
{
	u64 whole_dev_bytes_n;
	u64 entries_bytes_n;

	init_once();
	options_parse(argc, argv);
	out_pf("block device path is %s, opening...\n", blk_dev.path);
	blk_dev.fd = open(blk_dev.path, O_RDWR | O_SYNC);
	if (blk_dev.fd == -1)
		error_pf("%s:unable to open\n", blk_dev.path);
	out_pf("%s:opened\n", blk_dev.path);

	sysfs_infos_get();

	whole_dev_bytes_n = blk_dev.sz_512_n * 512;
	if ((whole_dev_bytes_n % blk_dev.logical_blk_bytes_n) != 0)
		error_pf("%s: the whole device size %"PRIu64" is not a multiple of the logical block size %"PRIu64" bytes\n", whole_dev_bytes_n, blk_dev.logical_blk_bytes_n);
	/* the total number of lba, -1 to get the offset of the last one */
	blk_dev.last_lba = blk_dev.sz_512_n * 512 / blk_dev.logical_blk_bytes_n - 1;
	out_pf("%s:last lba is %"PRIu64"\n", blk_dev.path, blk_dev.last_lba);

	if (load_previous_gpt) {
		previous_gpt_load();
		build_data_from_previous_gpt();
	}
	/*
	 * Resizing the array of entries is expensive and dangerous, try to
	 * keep it constant as much as possible.
	 * If it was not initialized based from the previous gpt, init one
	 * with sane defaults.
	 */
	if (entries_n == 0)
		entries_reset();
	if (hdrs.bytes_n == 0)
		hdrs.bytes_n = HDR_BYTES_N;
	if (guid_is_zero(&hdrs.disk_guid))
		guid_randomize(&hdrs.disk_guid);
	/*
	 * Once we have "an array of entries", the first and last usable
	 * lbas must be recomputed.
	 * We ignore the values from the previous gpt headers, we prefer
	 * to recompute them.
	 */
	hdrs_usable_lbas_compute();
	input_line_loop();
	return 0;
}
/*{{{ preprocessor space cleanup */
#undef ARRAY_N
#undef BLK_0
#undef BLK_1
#undef BLK_2
#undef BLK_3
#undef BLK_4
#undef BOLD_RED
#undef BOLD_ORANGE
#undef BOLD
#undef RESTORE
#undef BOOT_SIGNATURE_0X55
#undef BOOT_SIGNATURE_0XAA
#undef ENTRIES_ARRAY_MIN_BYTES_N
#undef ENTRY_BYTES_N
#undef HDR_BYTES_N
#undef HDR_REVISION
#undef HDR_SIGNATURE
#undef HDR_SIGNATURE_BYTES_N
#undef loop
#undef PART_0
#undef s32
#undef STATIC
#undef strtou64
#undef u8 
#undef u16
#undef u32
#undef u64
#undef utf16
#undef utf8
#undef X64_UTF8_BYTES_MAX
/*}}} preprocessor space cleanup -- end */
/*{{{ namespace cleanup ------------------------------------------------------*/
#undef blk_dev
#undef build_data_from_previous_gpt
#undef change_disk_guid_prompt
#undef colors_on
#undef delete_entry_prompt
#undef edit_entry
#undef edit_entry_attrs_prompt
#undef edit_entry_first_lba_prompt
#undef edit_entry_last_lba_prompt
#undef edit_entry_prompt
#undef edit_entry_to_entries
#undef edit_entry_type_prompt
#undef edit_entry_uniq_guid_prompt
#undef entries
#undef entries_lbas_n
#undef entries_load
#undef entries_n
#undef entries_reset
#undef entries_serialized_array
#undef entries_serialized_array_crc32
#undef entries_serialized_array_gen
#undef entries_serialized_array_gen_entry
#undef entries_serialized_array_primary_write
#undef entries_serialized_array_secondary_write
#undef entries_show
#undef entry_bytes_n
#undef entry_delete
#undef entry_is_used
#undef entry_show
#undef entry_t
#undef error_pf
#undef first_lba_default_select
#undef gpt_write
#undef guid_is_zero
#undef guid_t
#undef guid_randomize
#undef guid_read
#undef guid_read_from_input
#undef guid_write
#undef hdr_load
#undef hdr_show
#undef hdr_primary_serialized
#undef hdr_primary_serialized_gen
#undef hdr_primary_serialized_write
#undef hdr_secondary_serialized
#undef hdr_secondary_serialized_gen
#undef hdr_secondary_serialized_write
#undef hdr_t
#undef hdr_validate
#undef hdrs
#undef hdrs_usable_lbas_compute
#undef init_once
#undef input_line
#undef input_line_consume
#undef input_line_loop
#undef input_state
#undef last_lba_default_select
#undef le_crc32_tbl_gen
#undef le_crc32_update
#undef load_previous_gpt
#undef main_menu_prompt
#undef main_menu_show
#undef options_parse
#undef out_bold_pf
#undef out_guid
#undef out_pf
#undef gpt_load
#undef protective_mbr
#undef protective_mbr_gen
#undef protective_mbr_write
#undef read_full
#undef state_change_disk_guid
#undef state_delete_entry
#undef state_edit_entry
#undef state_edit_entry_substate_attrs
#undef state_edit_entry_substate_first_lba
#undef state_edit_entry_substate_last_lba
#undef state_edit_entry_substate_type
#undef state_edit_entry_substate_uniq_quid
#undef state_main_menu
#undef sysfs_infos_get
#undef type_guids_lookup_name
#undef types
#undef types_show
#undef usage
#undef utf16_strdup
#undef warning_pf
#undef write_full
/*----------------------------------------------------------------------------*/
#undef nyangpt_main
/*}}} namespace cleanup -- end -----------------------------------------------*/
#endif


Mode Type Size Ref File
100644 blob 50694 740386be2905ab4c88367e3dccb318718afca674 nyangpt.c
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/sylware/nyangpt

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/sylware/nyangpt

Clone this repository using git:
git clone git://git.rocketgit.com/user/sylware/nyangpt

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main