#ifndef NYANGPT_C
#define NYANGPT_C
/*
* Copyright 2021 Sylvain BERTRAND <sylvain.bertrand@legeek.net>
* LICENSE: GNU AFFERO GPLV3
*/
/*
* quick and dirty, ultra minimal, source oriented, UEFI GPT partition creator
*/
#include <stdarg.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
*/
#define utf8 uint8_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(;;)
/******************************************************************************/
/* 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;
}
}
/******************************************************************************/
/*----------------------------------------------------------------------------*/
static struct {
utf8 *path;
u64 sz_512_n;
u64 logical_blk_sz;
u64 physical_blk_sz;
int fd;
u64 last_lba;
} blk_dev;
/*----------------------------------------------------------------------------*/
static u8 *protective_mbr;
/* offsets */
#define BOOT_SIGNATURE_0X55 0x1fe
#define BOOT_SIGNATURE_0XAA 0x1ff
#define PART_0 0x1be
/*----------------------------------------------------------------------------*/
struct guid_t {
u32 blk_0;
u16 blk_1;
u16 blk_2;
u16 blk_3;
u8 blk_4[6];
};
#define BLK_0 0x0
#define BLK_1 0x4
#define BLK_2 0x6
#define BLK_3 0x8
#define BLK_4 0xa
struct entry_t {
struct guid_t type;
struct guid_t uniq;
u64 first;
u64 last;
u64 attrs;
/* utf-16 names? really?... */
};
#define ENTRIES_ARRAY_MIN_BYTES_N 16384 /* specs: minimum to book on the disk */
#define ENTRY_BYTES_N 0x80
/* make it at least 128MB */
static struct entry_t entry_efi_system =
{
.type.blk_0 = 0xc12a7328,
.type.blk_1 = 0xf81f,
.type.blk_2 = 0x11d2,
.type.blk_3 = 0xba4b,
.type.blk_4 = {0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b},
.uniq.blk_0 = 0xdeadbeef,
.uniq.blk_1 = 0x0000,
.uniq.blk_2 = 0x0000,
.uniq.blk_3 = 0x0000,
.uniq.blk_4 = {0x00, 0x0, 0x00, 0x00, 0x00, 0x01},
.first = 0x0000000000000022,
.last = 0x000000000004ffff,
.attrs = 0,
};
static struct entry_t entry_root =
{
/* linux root x86_64 GUID */
.type.blk_0 = 0x4f68bce3,
.type.blk_1 = 0xe8cd,
.type.blk_2 = 0x4db1,
.type.blk_3 = 0x96e7,
.type.blk_4 = {0xfb, 0xca, 0xf9, 0x84, 0xb7, 0x09},
.uniq.blk_0 = 0xdeadbeef,
.uniq.blk_1 = 0x0000,
.uniq.blk_2 = 0x0000,
.uniq.blk_3 = 0x0000,
.uniq.blk_4 = {0x00, 0x0, 0x00, 0x00, 0x00, 0x02},
/* everything else */
.first = 0x0000000000050000,
.last = 0x0, /* generated later */
.attrs = 0,
};
static struct entry_t *entries[2] = {
&entry_efi_system,
&entry_root,
};
static u8 entries_n = 2;
static u8 entries_lbas_n;
static u8 *entries_array;
static u32 entries_array_crc32;
/*----------------------------------------------------------------------------*/
struct hdrs_t { /* there are 2 headers, each must fit in a logical block */
u64 first_usable_lba;
u64 last_usable_lba;
struct guid_t disk;
};
static struct hdrs_t hdrs = {
.disk.blk_0 = 0xdeadbeef,
.disk.blk_1 = 0x0000,
.disk.blk_2 = 0x0000,
.disk.blk_3 = 0x0000,
.disk.blk_4 = {0x00, 0x0, 0x00, 0x00, 0x00, 0x00},
};
static u8 hdr_signature[8] = "EFI PART";
#define HDR_REVISION 0x00010000 /* 1.0 */
#define HDR_BYTES_N 0x5c /* 92 bytes */
static u8 *hdr_primary;
static u8 *hdr_secondary;
/*----------------------------------------------------------------------------*/
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;
}
/* 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];
}
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_sz_path;
utf8 *physical_blk_sz_path;
blk_dev_name = strrchr(blk_dev.path, '/');
++blk_dev_name;
printf("%s:device name is %s\n", blk_dev.path, blk_dev_name);
sz_512_n_path = pf("/sys/block/%s/size", blk_dev_name);
printf("%s:reading %s\n", blk_dev.path, sz_512_n_path);
fd = open(sz_512_n_path, O_RDONLY);
if (fd == -1) {
dprintf(2, "%s:unable to open %s\n", blk_dev.path, sz_512_n_path);
exit(1);
}
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);
printf("%s:size is %"PRIu64" blocks of 512 bytes\n", blk_dev.path, blk_dev.sz_512_n);
close(fd);
logical_blk_sz_path = pf("/sys/block/%s/queue/logical_block_size", blk_dev_name);
printf("%s:reading %s\n", blk_dev.path, logical_blk_sz_path);
fd = open(logical_blk_sz_path, O_RDONLY);
if (fd == -1) {
dprintf(2, "%s:unable to open %s\n", blk_dev.path, logical_blk_sz_path);
exit(1);
}
free(logical_blk_sz_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_sz = strtou64(val_str, 0, 10);
printf("%s:logical block size is %"PRIu64" bytes\n", blk_dev.path, blk_dev.logical_blk_sz);
close(fd);
physical_blk_sz_path = pf("/sys/block/%s/queue/physical_block_size", blk_dev_name);
printf("%s:reading %s\n", blk_dev.path, physical_blk_sz_path);
fd = open(physical_blk_sz_path, O_RDONLY);
if (fd == -1) {
dprintf(2, "%s:unable to open %s\n", blk_dev.path, physical_blk_sz_path);
exit(1);
}
free(physical_blk_sz_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_sz = strtou64(val_str, 0, 10);
printf("%s:physical block size is %"PRIu64" bytes\n", blk_dev.path, blk_dev.physical_blk_sz);
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_sz < 512) {
dprintf(2, "%s: something is wrong, the logical block size is %"PRIu64", below 512/0x200 bytes", blk_dev.path, blk_dev.logical_blk_sz);
exit(1);
}
protective_mbr = calloc(1, blk_dev.logical_blk_sz);
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_sz;
/* 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;
size_t written_bytes_n;
r0 = lseek(blk_dev.fd, 0, SEEK_SET);
if (r0 != 0) {
dprintf(2, "%s:unable to reach the start of the device\n", blk_dev.path);
exit(1);
}
/* short writes */
written_bytes_n = 0;
loop {
ssize_t r1;
errno = 0;
r1 = write(blk_dev.fd, protective_mbr + written_bytes_n, blk_dev.logical_blk_sz - written_bytes_n);
if (r1 == -1) {
if (errno == EINTR)
continue;
dprintf(2, "%s:unable to write the protective master boot record (mbr), device mbr is now probably corrupted\n", blk_dev.path);
exit(1);
}
written_bytes_n += (size_t)r1;
if (written_bytes_n == (size_t)(blk_dev.logical_blk_sz))
break;
}
printf("%s:protective master boot record (mbr) written, %"PRIu64" bytes\n", blk_dev.path, blk_dev.logical_blk_sz);
}
static u8 entries_array_gen_entry(u8 entry_idx)
{
u8 *entry;
u64 *p64;
entry = entries_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_array_gen(void)
{
u8 entry_idx;
entries_array = calloc(1, entries_lbas_n * blk_dev.logical_blk_sz);
entry_idx = 0;
loop {
if (entry_idx == entries_n)
break;
entries_array_gen_entry(entry_idx);
++entry_idx;
}
}
static void hdr_primary_gen(void)
{
u64 *p64;
u32 *p32;
u16 *p16;
u32 le_crc32;
hdr_primary = calloc(1, blk_dev.logical_blk_sz);
memcpy(hdr_primary, hdr_signature, 8);
p32 = (u32*)&hdr_primary[0x08];
*p32 = htole32(HDR_REVISION);
p32 =(u32*)&hdr_primary[0x0c];
*p32 = htole32(HDR_BYTES_N);
/* the CRC32 will go there, 0 for its calculation */
p64 = (u64*)&hdr_primary[0x18];
*p64 = htole64(0x00000001); /* lba of this hdr */
p64 = (u64*)&hdr_primary[0x20];
*p64 = htole64(blk_dev.last_lba); /* the hdr at the end */
p64 = (u64*)&hdr_primary[0x28];
*p64 = htole64(hdrs.first_usable_lba);
p64 = (u64*)&hdr_primary[0x30];
*p64 = htole64(hdrs.last_usable_lba);
guid_write(&hdr_primary[0x38], &hdrs.disk);
p64 = (u64*)&hdr_primary[0x48];
*p64 = htole64(2); /* skip mbr and hdr */
p32 = (u32*)&hdr_primary[0x50];
*p32 = htole32((u32)(ENTRIES_ARRAY_MIN_BYTES_N/ENTRY_BYTES_N));
p32 = (u32*)&hdr_primary[0x54];
*p32 = htole32(ENTRY_BYTES_N);
p32 = (u32*)&hdr_primary[0x58];
*p32 = htole32(entries_array_crc32);
/* crc32 on exactly the header size */
le_crc32 = 0;
le_crc32_update(hdr_primary, HDR_BYTES_N, &le_crc32);
printf("%s:primary hdr crc32 is 0x%"PRIx32"\n", blk_dev.path, le_crc32);
p32 = (u32*)&hdr_primary[0x10];
*p32 = le_crc32;
}
static void hdr_secondary_gen(void)
{
u64 *p64;
u32 *p32;
u16 *p16;
u32 le_crc32;
hdr_secondary = calloc(1, blk_dev.logical_blk_sz);
memcpy(hdr_secondary, hdr_signature, 8);
p32 = (u32*)&hdr_secondary[0x08];
*p32 = htole32(HDR_REVISION);
p32 =(u32*)&hdr_secondary[0x0c];
*p32 = htole32(HDR_BYTES_N);
/* the CRC32 will go there, 0 for its calculation */
p64 = (u64*)&hdr_secondary[0x18];
*p64 = htole64(blk_dev.last_lba); /* this hdr */
p64 = (u64*)&hdr_secondary[0x20];
*p64 = htole64(1); /* the hdr at the beginning */
p64 = (u64*)&hdr_secondary[0x28];
*p64 = htole64(hdrs.first_usable_lba);
p64 = (u64*)&hdr_secondary[0x30];
*p64 = htole64(hdrs.last_usable_lba);
guid_write(&hdr_secondary[0x38], &hdrs.disk);
p64 = (u64*)&hdr_secondary[0x48];
*p64 = htole64(blk_dev.last_lba - entries_lbas_n);
p32 = (u32*)&hdr_secondary[0x50];
*p32 = htole32((u32)(ENTRIES_ARRAY_MIN_BYTES_N/ENTRY_BYTES_N));
p32 = (u32*)&hdr_secondary[0x54];
*p32 = htole32(ENTRY_BYTES_N);
p32 = (u32*)&hdr_secondary[0x58];
*p32 = htole32(entries_array_crc32);
/* crc32 on exactly the header size */
le_crc32 = 0;
le_crc32_update(hdr_secondary, HDR_BYTES_N, &le_crc32);
printf("%s:secondary hdr crc32 is 0x%"PRIx32"\n", blk_dev.path, le_crc32);
p32 = (u32*)&hdr_secondary[0x10];
*p32 = le_crc32;
}
static void primary_hdr_write(void)
{
off_t r0;
off_t start;
size_t written_bytes_n;
size_t bytes_to_write_n;
/* skip the mbr */
start = (off_t)blk_dev.logical_blk_sz;
r0 = lseek(blk_dev.fd, start, SEEK_SET);
if (r0 != start) {
dprintf(2, "%s:unable to reach the first lba of the device\n", blk_dev.path);
exit(1);
}
bytes_to_write_n = (size_t)(blk_dev.logical_blk_sz);
/* short writes */
written_bytes_n = 0;
loop {
ssize_t r1;
errno = 0;
r1 = write(blk_dev.fd, hdr_primary + written_bytes_n, bytes_to_write_n - written_bytes_n);
if (r1 == -1) {
if (errno == EINTR)
continue;
dprintf(2, "%s:unable to write the primary GPT header, block device is now probably corrupted\n", blk_dev.path);
exit(1);
}
written_bytes_n += (size_t)r1;
if (written_bytes_n == bytes_to_write_n)
break;
}
printf("%s:primary GPT header written, %"PRIu64" bytes\n", blk_dev.path, bytes_to_write_n);
}
static void secondary_hdr_write(void)
{
off_t r0;
off_t start;
size_t written_bytes_n;
size_t bytes_to_write_n;
start = (off_t)(blk_dev.last_lba * blk_dev.logical_blk_sz);
r0 = lseek(blk_dev.fd, start, SEEK_SET);
if (r0 != start) {
dprintf(2, "%s:unable to reach the lba of the secondary header\n", blk_dev.path);
exit(1);
}
bytes_to_write_n = (size_t)(blk_dev.logical_blk_sz);
/* short writes */
written_bytes_n = 0;
loop {
ssize_t r1;
errno = 0;
r1 = write(blk_dev.fd, hdr_secondary + written_bytes_n, bytes_to_write_n - written_bytes_n);
if (r1 == -1) {
if (errno == EINTR)
continue;
dprintf(2, "%s:unable to write the primary GPT header, block device is now probably corrupted\n", blk_dev.path);
exit(1);
}
written_bytes_n += (size_t)r1;
if (written_bytes_n == bytes_to_write_n)
break;
}
printf("%s:secondary GPT header written, %"PRIu64" bytes\n", blk_dev.path, bytes_to_write_n);
}
static void primary_entries_write(void)
{
off_t r0;
size_t written_bytes_n;
off_t start;
size_t bytes_to_write_n;
/* skip the mbr and the primary hdr */
start = (off_t)(blk_dev.logical_blk_sz * 2);
r0 = lseek(blk_dev.fd, start, SEEK_SET);
if (r0 != start) {
dprintf(2, "%s:unable to reach the first lba for the primary entries\n", blk_dev.path);
exit(1);
}
bytes_to_write_n = (size_t)(blk_dev.logical_blk_sz * entries_lbas_n);
/* short writes */
written_bytes_n = 0;
loop {
ssize_t r1;
errno = 0;
r1 = write(blk_dev.fd, entries_array + written_bytes_n, bytes_to_write_n - written_bytes_n);
if (r1 == -1) {
if (errno == EINTR)
continue;
dprintf(2, "%s:unable to write the primary entries, block device is now probably corrupted\n", blk_dev.path);
exit(1);
}
written_bytes_n += (size_t)r1;
if (written_bytes_n == bytes_to_write_n)
break;
}
printf("%s:primary entries written, %"PRIu64" bytes\n", blk_dev.path, bytes_to_write_n);
}
static void secondary_entries_write(void)
{
off_t r0;
size_t written_bytes_n;
off_t start;
size_t bytes_to_write_n;
/* skip the secondary hdr, offset arithmetic */
start = (off_t)(blk_dev.logical_blk_sz * (blk_dev.last_lba
- entries_lbas_n));
r0 = lseek(blk_dev.fd, start, SEEK_SET);
if (r0 != start) {
dprintf(2, "%s:unable to reach the first lba for the secondary entries\n", blk_dev.path);
exit(1);
}
bytes_to_write_n = (size_t)(blk_dev.logical_blk_sz * entries_lbas_n);
/* short writes */
written_bytes_n = 0;
loop {
ssize_t r1;
errno = 0;
r1 = write(blk_dev.fd, entries_array + written_bytes_n, bytes_to_write_n - written_bytes_n);
if (r1 == -1) {
if (errno == EINTR)
continue;
dprintf(2, "%s:unable to write the secondary entries, block device is now probably corrupted\n", blk_dev.path);
exit(1);
}
written_bytes_n += (size_t)r1;
if (written_bytes_n == bytes_to_write_n)
break;
}
printf("%s:secondary entries written, %"PRIu64" bytes\n", blk_dev.path, bytes_to_write_n);
}
int main(int argc, utf8 **argv)
{
u64 whole_dev_bytes_n;
u64 entries_bytes_n;
if (argc < 2) {
dprintf(2, "missing block device path\n");
return 1;
}
blk_dev.path = argv[1];
printf("block device path is %s\n", blk_dev.path);
blk_dev.fd = open(blk_dev.path, O_RDWR | O_SYNC);
if (blk_dev.fd == -1) {
dprintf(2, "%s:unable to open\n", blk_dev.path);
return 1;
}
printf("%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_sz) != 0) {
dprintf(2, "%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_sz);
exit(1);
}
/* 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_sz - 1;
printf("%s:last lba is %"PRIu64"\n", blk_dev.path, blk_dev.last_lba);
entries_bytes_n = ENTRY_BYTES_N * entries_n;
/* we handle entries array size of ENTRIES_ARRAY_MIN_BYTES_N */
if (entries_bytes_n > ENTRIES_ARRAY_MIN_BYTES_N) {
dprintf(2, "%s:sorry,you have too many partition entries, %u, for this tool to handle\n", blk_dev.path, entries_n);
exit(1);
}
entries_lbas_n = ENTRIES_ARRAY_MIN_BYTES_N / blk_dev.logical_blk_sz
+ ((ENTRIES_ARRAY_MIN_BYTES_N % blk_dev.logical_blk_sz) == 0
? 0 : 1);
/* 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;
printf("%s:lbas for partitions:first usable is %"PRIu64", last usable is %"PRIu64"\n", blk_dev.path, hdrs.first_usable_lba, hdrs.last_usable_lba);
entry_root.last = hdrs.last_usable_lba;
entries_array_gen();
le_crc32_tbl_gen();
/* crc32 on exactly the entries array size, not lba bounded */
entries_array_crc32 = 0;
le_crc32_update(entries_array, (ENTRIES_ARRAY_MIN_BYTES_N/ENTRY_BYTES_N)
* ENTRY_BYTES_N, &entries_array_crc32);
printf("%s:entries array crc32 is 0x%"PRIx32"\n", blk_dev.path, entries_array_crc32);
hdr_primary_gen();
hdr_secondary_gen();
protective_mbr_gen();
protective_mbr_write();
primary_hdr_write();
secondary_hdr_write();
primary_entries_write();
secondary_entries_write();
return 0;
}
#endif