#ifndef GRUB_MAPLIB_VDISK_H
#define GRUB_MAPLIB_VDISK_H

/*
 * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include <grub/misc.h>
#include <grub/file.h>
#include <stdint.h>

/** Number of cylinders */
#define VDISK_CYLINDERS 1024 /* Maximum possible */

/** Number of heads */
#define VDISK_HEADS 255

/** Number of sectors per track */
#define VDISK_SECTORS_PER_TRACK 63

/** Sector size (in bytes) */
#define VDISK_SECTOR_SIZE 512

/** Partition start LBA */
#define VDISK_PARTITION_LBA 128

/** Cluster size (in sectors) */
#define VDISK_CLUSTER_COUNT 64

/** Cluster size (in bytes) */
#define VDISK_CLUSTER_SIZE (VDISK_CLUSTER_COUNT * VDISK_SECTOR_SIZE)

/** Number of clusters */
#define VDISK_CLUSTERS 0x03ffc000ULL /* Fill 2TB disk */

/** Maximum number of virtual files
 *
 * The total number of files must be strictly less than the number of
 * sectors per cluster.
 */
#define VDISK_MAX_FILES (VDISK_CLUSTER_COUNT - 1)

/** Maximum file size (in sectors) */
#define VDISK_FILE_COUNT 0x800000UL /* max for 32-bit address space */

/** Maximum file size (in clusters) */
#define VDISK_FILE_CLUSTERS (VDISK_FILE_COUNT / VDISK_CLUSTER_COUNT)

/** File starting LBA */
#define VDISK_FILE_LBA(idx) (((idx) + 1) * VDISK_FILE_COUNT)

/** File index from LBA */
#define VDISK_FILE_IDX(lba) (((lba) / VDISK_FILE_COUNT) - 1)

/** File offset (in bytes) from LBA */
#define VDISK_FILE_OFFSET(lba)    \
  (((lba) % VDISK_FILE_COUNT) * VDISK_SECTOR_SIZE)

/** File index from directory entry LBA */
#define VDISK_FILE_DIRENT_IDX(lba) (((lba) - 1) % VDISK_CLUSTER_COUNT)

/** Number of sectors allocated for FAT */
#define VDISK_SECTORS_PER_FAT   \
  (((VDISK_CLUSTERS * sizeof (uint32_t) +     \
     VDISK_CLUSTER_SIZE - 1) / VDISK_CLUSTER_SIZE) * VDISK_CLUSTER_COUNT)

/** Number of reserved sectors */
#define VDISK_RESERVED_COUNT VDISK_CLUSTER_COUNT

/** Starting cluster number for file */
#define VDISK_FILE_CLUSTER(idx)    \
  ((((VDISK_FILE_COUNT - VDISK_PARTITION_LBA -    \
      VDISK_RESERVED_COUNT - VDISK_SECTORS_PER_FAT) / \
      VDISK_CLUSTER_COUNT) + 2) + ((idx) * VDISK_FILE_CLUSTERS))

/** Total number of sectors within partition */
#define VDISK_PARTITION_COUNT   \
  (VDISK_RESERVED_COUNT + VDISK_SECTORS_PER_FAT +   \
   (VDISK_CLUSTERS * VDISK_CLUSTER_COUNT))

/** Number of sectors */
#define VDISK_COUNT (VDISK_PARTITION_LBA + VDISK_PARTITION_COUNT)

/** Calculate sector from cluster */
#define VDISK_CLUSTER_SECTOR(cluster)          \
  ((((cluster) - 2) * VDISK_CLUSTER_COUNT) +      \
    VDISK_RESERVED_COUNT + VDISK_SECTORS_PER_FAT)

/*****************************************************************************
 *
 * Master Boot Record
 *
 *****************************************************************************
 */

/** Master Boot Record LBA */
#define VDISK_MBR_LBA 0x00000000

/** Master Boot Record sector count */
#define VDISK_MBR_COUNT 1

/** Partition table entry */
struct vfat_partition
{
  /** Bootable flag */
  uint8_t bootable;
  /** C/H/S start address */
  uint8_t chs_start[3];
  /** System indicator (partition type) */
  uint8_t type;
  /** C/H/S end address */
  uint8_t chs_end[3];
  /** Linear start address */
  uint32_t start;
  /** Linear length */
  uint32_t length;
} __attribute__ ((packed));

/** Master Boot Record */
struct vfat_mbr
{
  /** Code area */
  uint8_t code[440];
  /** Disk signature */
  uint32_t signature;
  /** Padding */
  uint8_t pad[2];
  /** Partition table */
  struct vfat_partition partitions[4];
  /** 0x55aa signature */
  uint16_t magic;
} __attribute__ ((packed));

/** MBR boot partition indiciator */
#define VDISK_MBR_BOOTABLE 0x80

/** MBR type indicator for FAT32 */
#define VDISK_MBR_TYPE_FAT32 0x0c

/** MBR signature */
#define VDISK_MBR_SIGNATURE 0x19260817

/** MBR magic */
#define VDISK_MBR_MAGIC 0xaa55

/*****************************************************************************
 *
 * Volume Boot Record
 *
 *****************************************************************************
 */

/** Volume Boot Record LBA */
#define VDISK_VBR_LBA VDISK_PARTITION_LBA

/** Volume Boot Record sector count */
#define VDISK_VBR_COUNT 1

/** Volume Boot Record */
struct vfat_vbr
{
  /** Jump instruction */
  uint8_t jump[3];
  /** OEM identifier */
  char oemid[8];
  /** Number of bytes per sector */
  uint16_t bytes_per_sector;
  /** Number of sectors per cluster */
  uint8_t sectors_per_cluster;
  /** Number of reserved sectors */
  uint16_t reserved_sectors;
  /** Number of FATs */
  uint8_t fats;
  /** Number of root directory entries (FAT12/FAT16 only) */
  uint16_t root_directory_entries;
  /** Total number of sectors (0 if more than 65535) */
  uint16_t sectors_short;
  /** Media descriptor type */
  uint8_t media;
  /** Number of sectors per FAT (FAT12/FAT16 only) */
  uint16_t sectors_per_fat_short;
  /** Number of sectors per track */
  uint16_t sectors_per_track;
  /** Number of heads */
  uint16_t heads;
  /** Number of hidden sectors (i.e. LBA of start of partition) */
  uint32_t hidden_sectors;
  /** Total number of sectors */
  uint32_t sectors;
  /* FAT32-specific fields */
  /** Sectors per FAT */
  uint32_t sectors_per_fat;
  /** Flags */
  uint16_t flags;
  /** FAT version number */
  uint16_t version;
  /** Root directory cluster */
  uint32_t root;
  /** FSInfo sector */
  uint16_t fsinfo;
  /** Backup boot sector */
  uint16_t backup;
  /** Reserved */
  uint8_t reserved[12];
  /** Drive number */
  uint8_t drive;
  /** Windows NT flags */
  uint8_t nt_flags;
  /** Signature */
  uint8_t signature;
  /** Volume ID serial */
  uint32_t serial;
  /** Label (space-padded) */
  char label[11];
  /** System identifier */
  char system[8];
  /** Boot code */
  uint8_t code[420];
  /** 0x55aa signature */
  uint16_t magic;
} __attribute__ ((packed));

/** VBR jump instruction
 *
 * bootmgr.exe will actually fail unless this is present.  Someone
 * must read specification documents without bothering to understand
 * what's really happening.
 */
#define VDISK_VBR_JUMP_WTF_MS 0xe9

/** VBR OEM ID */
#define VDISK_VBR_OEMID "grubfat\0"

/** VBR media type */
#define VDISK_VBR_MEDIA 0xf8

/** VBR signature */
#define VDISK_VBR_SIGNATURE 0x29

/** VBR serial number */
#define VDISK_VBR_SERIAL 0xdeadc0de

/** VBR label */
#define VDISK_VBR_LABEL "grubvfat   "

/** VBR system identifier */
#define VDISK_VBR_SYSTEM "FAT32   "

/** VBR magic */
#define VDISK_VBR_MAGIC 0xaa55

/*****************************************************************************
 *
 * FSInfo
 *
 *****************************************************************************
 */

/** FSInfo sector */
#define VDISK_FSINFO_SECTOR 0x00000001

/** FSInfo LBA */
#define VDISK_FSINFO_LBA (VDISK_VBR_LBA + VDISK_FSINFO_SECTOR)

/** FSInfo sector count */
#define VDISK_FSINFO_COUNT 1

/** FSInfo */
struct vfat_fsinfo
{
  /** First signature */
  uint32_t magic1;
  /** Reserved */
  uint8_t reserved_1[480];
  /** Second signature */
  uint32_t magic2;
  /** Free cluster count */
  uint32_t free_count;
  /** Next free cluster */
  uint32_t next_free;
  /** Reserved */
  uint8_t reserved_2[12];
  /** Third signature */
  uint32_t magic3;
} __attribute__ ((packed));

/** FSInfo first signature */
#define VDISK_FSINFO_MAGIC1 0x41615252

/** FSInfo second signature */
#define VDISK_FSINFO_MAGIC2 0x61417272

/** FSInfo next free cluster */
#define VDISK_FSINFO_NEXT_FREE 0xffffffff /* No free clusters */

/** FSInfo third signature */
#define VDISK_FSINFO_MAGIC3 0xaa550000

/*****************************************************************************
 *
 * Backup Volume Boot Record
 *
 *****************************************************************************
 */

/** Backup Volume Boot Record sector */
#define VDISK_BACKUP_VBR_SECTOR 0x00000006

/** Backup Volume Boot Record LBA */
#define VDISK_BACKUP_VBR_LBA (VDISK_VBR_LBA + VDISK_BACKUP_VBR_SECTOR)

/** Backup Volume Boot Record sector count */
#define VDISK_BACKUP_VBR_COUNT 1

/*****************************************************************************
 *
 * File Allocation Table
 *
 *****************************************************************************
 */

/** FAT sector */
#define VDISK_FAT_SECTOR VDISK_RESERVED_COUNT

/** FAT LBA */
#define VDISK_FAT_LBA (VDISK_VBR_LBA + VDISK_FAT_SECTOR)

/** FAT sector count */
#define VDISK_FAT_COUNT VDISK_SECTORS_PER_FAT

/** FAT end marker */
#define VDISK_FAT_END_MARKER 0x0ffffff8

/*****************************************************************************
 *
 * Directory entries
 *
 *****************************************************************************
 */

/** An 8.3 filename record */
struct vfat_short_filename
{
  /** Filename */
  union
  {
    /** Structured 8.3 base name and extension */
    struct
    {
      /** Base name */
      char base[8];
      /** Extension */
      char ext[3];
    } __attribute__ ((packed));
    /** Raw bytes */
    uint8_t raw[11];
  } filename;
  /** Attributes */
  uint8_t attr;
  /** Reserved */
  uint8_t reserved;
  /** Creation time in tenths of a second */
  uint8_t created_deciseconds;
  /** Creation time (HMS packed) */
  uint16_t created_time;
  /** Creation date (YMD packed) */
  uint16_t created_date;
  /** Last accessed date (YMD packed) */
  uint16_t accessed_date;
  /** High 16 bits of starting cluster number */
  uint16_t cluster_high;
  /** Modification time (HMS packed) */
  uint16_t modified_time;
  /** Modification date (YMD packed) */
  uint16_t modified_date;
  /** Low 16 bits of starting cluster number */
  uint16_t cluster_low;
  /** Size */
  uint32_t size;
} __attribute__ ((packed));

/** A long filename record */
struct vfat_long_filename
{
  /** Sequence number */
  uint8_t sequence;
  /** Name characters */
  uint16_t name_1[5];
  /** Attributes */
  uint8_t attr;
  /** Type */
  uint8_t type;
  /** Checksum of 8.3 name */
  uint8_t checksum;
  /** Name characters */
  uint16_t name_2[6];
  /** Reserved */
  uint16_t reserved;
  /** Name characters */
  uint16_t name_3[2];
} __attribute__ ((packed));

/** Directory entry attributes */
enum vfat_directory_entry_attributes
{
  VDISK_READ_ONLY = 0x01,
  VDISK_HIDDEN = 0x02,
  VDISK_SYSTEM = 0x04,
  VDISK_VOLUME_LABEL = 0x08,
  VDISK_DIRECTORY = 0x10,
};

/** Long filename end-of-sequence marker */
#define VDISK_LFN_END 0x40

/** Long filename attributes */
#define VDISK_LFN_ATTR \
  (VDISK_READ_ONLY | VDISK_HIDDEN | VDISK_SYSTEM | VDISK_VOLUME_LABEL)

/** A directory entry */
union vfat_directory_entry
{
  /** Deleted file marker */
  uint8_t deleted;
  /** 8.3 filename */
  struct vfat_short_filename dos;
  /** Long filename */
  struct vfat_long_filename lfn;
} __attribute__ ((packed));

/** Magic marker for deleted files */
#define VDISK_DIRENT_DELETED 0xe5

/** Number of directory entries per sector */
#define VDISK_DIRENT_PER_SECTOR          \
  (VDISK_SECTOR_SIZE /          \
    sizeof (union vfat_directory_entry))

/** A directory sector */
struct vfat_directory
{
  /** Entries */
  union vfat_directory_entry entry[VDISK_DIRENT_PER_SECTOR];
} __attribute__ ((packed));

/*****************************************************************************
 *
 * Root directory
 *
 *****************************************************************************
 */

/** Root directory cluster */
#define VDISK_ROOT_CLUSTER 2

/** Root directory sector */
#define VDISK_ROOT_SECTOR VDISK_CLUSTER_SECTOR (VDISK_ROOT_CLUSTER)

/** Root directory LBA */
#define VDISK_ROOT_LBA (VDISK_VBR_LBA + VDISK_ROOT_SECTOR)

/*****************************************************************************
 *
 * Boot directory
 *
 *****************************************************************************
 */

/** Boot directory cluster */
#define VDISK_BOOT_CLUSTER 3

/** Boot directory sector */
#define VDISK_BOOT_SECTOR VDISK_CLUSTER_SECTOR (VDISK_BOOT_CLUSTER)

/** Boot directory LBA */
#define VDISK_BOOT_LBA (VDISK_VBR_LBA + VDISK_BOOT_SECTOR)

/*****************************************************************************
 *
 * Sources directory
 *
 *****************************************************************************
 */

/** Sources directory cluster */
#define VDISK_SOURCES_CLUSTER 4

/** Sources directory sector */
#define VDISK_SOURCES_SECTOR VDISK_CLUSTER_SECTOR (VDISK_SOURCES_CLUSTER)

/** Sources directory LBA */
#define VDISK_SOURCES_LBA (VDISK_VBR_LBA + VDISK_SOURCES_SECTOR)

/*****************************************************************************
 *
 * Fonts directory
 *
 *****************************************************************************
 */

/** Fonts directory cluster */
#define VDISK_FONTS_CLUSTER 5

/** Fonts directory sector */
#define VDISK_FONTS_SECTOR VDISK_CLUSTER_SECTOR (VDISK_FONTS_CLUSTER)

/** Fonts directory LBA */
#define VDISK_FONTS_LBA (VDISK_VBR_LBA + VDISK_FONTS_SECTOR)

/*****************************************************************************
 *
 * Resources directory
 *
 *****************************************************************************
 */

/** Resources directory cluster */
#define VDISK_RESOURCES_CLUSTER 6

/** Resources directory sector */
#define VDISK_RESOURCES_SECTOR VDISK_CLUSTER_SECTOR (VDISK_RESOURCES_CLUSTER)

/** Resources directory LBA */
#define VDISK_RESOURCES_LBA (VDISK_VBR_LBA + VDISK_RESOURCES_SECTOR)

/*****************************************************************************
 *
 * EFI directory
 *
 *****************************************************************************
 */

/** EFI directory cluster */
#define VDISK_EFI_CLUSTER 7

/** EFI directory sector */
#define VDISK_EFI_SECTOR VDISK_CLUSTER_SECTOR (VDISK_EFI_CLUSTER)

/** EFI directory LBA */
#define VDISK_EFI_LBA (VDISK_VBR_LBA + VDISK_EFI_SECTOR)

/*****************************************************************************
 *
 * Microsoft directory
 *
 *****************************************************************************
 */

/** Microsoft directory cluster */
#define VDISK_MICROSOFT_CLUSTER 8

/** Microsoft directory sector */
#define VDISK_MICROSOFT_SECTOR VDISK_CLUSTER_SECTOR (VDISK_MICROSOFT_CLUSTER)

/** Microsoft directory LBA */
#define VDISK_MICROSOFT_LBA (VDISK_VBR_LBA + VDISK_MICROSOFT_SECTOR)

/*****************************************************************************
 *
 * Files
 *
 *****************************************************************************
 */

/** Maximum virtual filename length (excluding NUL) */
#define VDISK_NAME_LEN 31

/** A virtual file */
struct vfat_file
{
  /** Filename */
  char name[ VDISK_NAME_LEN + 1 /* NUL */ ];
  /** Opaque token */
  void *opaque;
  /** Length (excluding any zero-padding) */
  size_t len;
  /** Length (including any zero-padding) */
  size_t xlen;
  /** Read data
   *
   * @v file    Virtual file
   * @v data    Data buffer
   * @v offset    Starting offset
   * @v len    Length
   */
  void (* read) (struct vfat_file *file, void *data, size_t offset,
        size_t len);
  /** Patch data (optional)
   *
   * @v file    Virtual file
   * @v data    Data buffer
   * @v offset    Starting offset
   * @v len    Length
   */
  void (* patch) (struct vfat_file *file, void *data, size_t offset,
         size_t len);
};

extern struct vfat_file vfat_files[VDISK_MAX_FILES];

void vfat_read (uint64_t lba, unsigned int count, void *data);
struct vfat_file *
vfat_add_file (const char *name, void *opaque, size_t len,
     void (* read) (struct vfat_file *file, void *data,
           size_t offset, size_t len));

void
vfat_patch_file (struct vfat_file *file,
       void (* patch) (struct vfat_file *file, void *data,
              size_t offset, size_t len));

/* vfat_grub */
struct grub_vfatdisk_file
{
  const char *name;
  grub_file_t file;
  struct grub_vfatdisk_file *next;
};
extern struct grub_vfatdisk_file *vfat_file_list;

void vfat_help (void);
void vfat_create (void);
void vfat_ls (void);
grub_size_t
vfat_replace_hex (char *addr, grub_size_t addr_len,
                  const char *search, grub_size_t search_len,
                  const char *replace, grub_size_t replace_len, int count);
void
vfat_patch_offset (const char *file, grub_size_t offset, const char *replace);
void
vfat_patch_search (const char *file, const char *search,
                   const char *replace, int count);
void
vfat_append_list (grub_file_t file, const char *file_name);

void
vfat_read_wrapper (struct vfat_file *vfile, void *data, size_t offset, size_t len);

#endif /* _VDISK_H */
