/* Minimal GPT parser
   Copyright (C) 2026 Alexander Larsson <alexl@redhat.com>

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.

   This file is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"

#include "gpt_parser.h"
#include "utils.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef HAVE_ENDIAN_H
#include <endian.h>
#endif
#ifdef HAVE_SYS_ENDIAN_H
#include <sys/endian.h>
#endif
#ifdef HAVE_MACHINE_ENDIAN_H
#include <machine/endian.h>
#endif

#define GPT_SECTOR_SIZE	     512
#define GPT_HEADER_OFFSET    GPT_SECTOR_SIZE
#define GPT_SIGNATURE	     0x5452415020494645ULL /* "EFI PART" */
#define GPT_ENTRY_NAME_CHARS 36

typedef struct attr_packed {
    uint64_t signature;
    uint32_t revision;
    uint32_t header_size;
    uint32_t header_crc32;
    uint32_t reserved;
    uint64_t my_lba;
    uint64_t alternate_lba;
    uint64_t first_usable_lba;
    uint64_t last_usable_lba;
    uint8_t disk_guid[16];
    uint64_t partition_entry_lba;
    uint32_t num_partition_entries;
    uint32_t partition_entry_size;
    uint32_t partition_entry_crc32;
} GptHeader;

typedef struct attr_packed {
    uint8_t type_guid[16];
    uint8_t unique_guid[16];
    uint64_t first_lba;
    uint64_t last_lba;
    uint64_t attributes;
    uint16_t name[GPT_ENTRY_NAME_CHARS];
} GptEntry;

static bool
is_zero_guid(const uint8_t *guid)
{
    for (int i = 0; i < 16; i++) {
	if (guid[i] != 0)
	    return false;
    }
    return true;
}

static void
utf16le_to_ascii(const uint16_t *src, char *dst, size_t max_chars)
{
    for (size_t i = 0; i < max_chars; i++) {
	uint16_t c = le16toh(src[i]);
	if (c == 0) {
	    dst[i] = '\0';
	    return;
	}
	dst[i] = (c < 128) ? (char)c : '?';
    }
    dst[max_chars] = '\0';
}

bool
gpt_parse(int fd, GptPartitionTable *out)
{
    GptHeader header;
    if (!full_pread(fd, &header, sizeof(header), GPT_HEADER_OFFSET))
	return false;

    if (le64toh(header.signature) != GPT_SIGNATURE) {
	error("Invalid GPT signature\n");
	return false;
    }

    uint32_t num_entries = le32toh(header.num_partition_entries);
    uint32_t entry_size = le32toh(header.partition_entry_size);
    uint64_t entries_offset = le64toh(header.partition_entry_lba) * GPT_SECTOR_SIZE;

    if (entry_size < sizeof(GptEntry)) {
	error("GPT entry size too small\n");
	return false;
    }

    autofree uint8_t *entry_buf = xmalloc(entry_size);

    autofree GptPartition *parts = NULL;
    uint32_t count = 0;

    for (uint32_t i = 0; i < num_entries; i++) {
	if (!full_pread(fd, entry_buf, entry_size, entries_offset + (uint64_t)i * entry_size))
	    return false;

	GptEntry *entry = (GptEntry *)entry_buf;
	if (is_zero_guid(entry->type_guid))
	    continue;

	parts = xrealloc(parts, (count + 1) * sizeof(GptPartition));

	uint16_t name_copy[GPT_ENTRY_NAME_CHARS];
	memcpy(name_copy, entry->name, sizeof(name_copy));
	utf16le_to_ascii(name_copy, parts[count].name, GPT_ENTRY_NAME_CHARS);
	uint64_t first = le64toh(entry->first_lba);
	uint64_t last = le64toh(entry->last_lba);
	parts[count].offset = first * GPT_SECTOR_SIZE;
	parts[count].size = (last - first + 1) * GPT_SECTOR_SIZE;
	count++;
    }

    out->partitions = steal_pointer(&parts);
    out->n_partitions = count;
    return true;
}

GptPartition *
gpt_find_partition(GptPartitionTable *table, const char *name)
{
    for (uint32_t i = 0; i < table->n_partitions; i++) {
	if (strcmp(table->partitions[i].name, name) == 0)
	    return &table->partitions[i];
    }
    return NULL;
}

void
gpt_partition_table_free(GptPartitionTable *table)
{
    free(table->partitions);
    table->partitions = NULL;
    table->n_partitions = 0;
}
