diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..8973a04 --- /dev/null +++ b/Android.mk @@ -0,0 +1,23 @@ +LOCAL_PATH:= $(call my-dir) + +common_cflags := -Wall -Werror -DANDROID +# XXX: nexus? +ifeq ($(BOARD_USES_QCOM_HARDWARE),true) +common_cflags += -DQCOM +endif + +include $(CLEAR_VARS) +LOCAL_MODULE := libgpt +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS := $(common_cflags) +LOCAL_SRC_FILES := util.c readline/readline.c crc32.c gpt.c +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := gpted +LOCAL_MODULE_TAGS := optional +LOCAL_SRC_FILES := gpted.c +LOCAL_CFLAGS := $(common_cflags) +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_STATIC_LIBRARIES := libc libgpt +include $(BUILD_EXECUTABLE) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cf8020f --- /dev/null +++ b/Makefile @@ -0,0 +1,91 @@ +# Makefile for gpted + +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +MANDIR ?= $(PREFIX)/share/man + +ifeq (,$(VERSION)) + VERSION := $(shell git rev-parse --short HEAD) + ifneq (,$(shell git diff-index --name-only HEAD)) + VERSION := $(VERSION)-dirty + endif +endif + +CC := gcc +AR := ar +LD := gcc +CFLAGS := -g -Wall -Werror +LDFLAGS := -g + +LIB_C_SRC := util.c crc32.c gpt.c +TARGET_LIB := libgpt.a + +EXE_C_SRC := gpted.c +TARGET_EXE := gpted + +ifeq ($(WITH_READLINE),true) + SYSLIBS := -lreadline +else + CFLAGS += -I. + LIB_C_SRC += readline/readline.c +endif + +LIB_C_OBJ := $(LIB_C_SRC:.c=.o) +EXE_C_OBJ := $(EXE_C_SRC:.c=.o) + +C_SRC := $(LIB_C_SRC) $(EXE_C_SRC) +C_OBJ := $(LIB_C_OBJ) $(EXE_C_OBJ) + +all: $(TARGET_EXE) + +define cmd_c_compile + $(CC) $(CPPFLAGS) $(CFLAGS) -MD -MP -MF .$(@F:.o=.d) -c -o $@ $< +endef +-include $(wildcard .*.d) + +%.o: %.c + $(cmd_c_compile) + +verdep := $(shell grep -l "^\#include \"version\.h\"" $(C_SRC)) +ifneq ($(verdep),) +$(verdep): version.h +endif + +$(TARGET_EXE): $(EXE_C_OBJ) $(TARGET_LIB) + $(LD) $(LDFLAGS) -o $@ $^ $(SYSLIBS) + +$(TARGET_LIB): $(LIB_C_OBJ) + $(AR) r $@ $^ + +version.h: + @echo "/* Autogenerated file. Do not edit. */" > .$@.tmp && \ + echo "#ifndef VERSION_H" >> .$@.tmp && \ + echo "#define PACKAGE \"$(TARGET)\"" >> .$@.tmp && \ + echo "#define VERSION \"$(VERSION)\"" >> .$@.tmp && \ + echo "#endif" >> .$@.tmp && \ + rm -f $@ && \ + mv -f .$@.tmp $@ + +tags: $(C_SRC) + ctags -R + +.PHONY: install + install -D gpted $(DESTDIR)$(BINDIR) + install -D gpted.1 $(DESTDIR)$(MANDIR)/man1 + +.PHONY: clean +clean: + rm -f core .*.d readline/*.o *.o *.a gpted + +.PHONY: distclean +distclean: clean + rm -f .*.d + +.PHONY: tarball +tarball: distclean + (d=`basename $PWD`; + cd ..; \ + cp -ar $d $d-$(VERSION); \ + tar czvf $d-$(VERSION).tar.gz `find $d-$(VERSION) -type f | egrep -v "CVS|debian|\.git"`; \ + rm -rf $d-$(VERSION); \ + cd $(PWD); diff --git a/crc32.c b/crc32.c new file mode 100644 index 0000000..870931a --- /dev/null +++ b/crc32.c @@ -0,0 +1,114 @@ +/*- + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + * + * First, the polynomial itself and its table of feedback terms. The + * polynomial is + * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 + * + * Note that we take it "backwards" and put the highest-order term in + * the lowest-order bit. The X^32 term is "implied"; the LSB is the + * X^31 term, etc. The X^0 term (usually shown as "+1") results in + * the MSB being 1 + * + * Note that the usual hardware shift register implementation, which + * is what we're using (we're merely optimizing it by doing eight-bit + * chunks at a time) shifts bits into the lowest-order term. In our + * implementation, that means shifting towards the right. Why do we + * do it this way? Because the calculated CRC must be transmitted in + * order from highest-order term to lowest-order term. UARTs transmit + * characters in order from LSB to MSB. By storing the CRC this way + * we hand it to the UART in the order low-byte to high-byte; the UART + * sends each low-bit to hight-bit; and the result is transmission bit + * by bit from highest- to lowest-order term without requiring any bit + * shuffling on our part. Reception works similarly + * + * The feedback terms table consists of 256, 32-bit entries. Notes + * + * The table can be generated at runtime if desired; code to do so + * is shown later. It might not be obvious, but the feedback + * terms simply represent the results of eight shift/xor opera + * tions for all combinations of data and CRC register values + * + * The values must be right-shifted by eight bits by the "updcrc + * logic; the shift must be unsigned (bring in zeroes). On some + * hardware you could probably optimize the shift in assembler by + * using byte-swap instructions + * polynomial $edb88320 + * + * + * CRC32 code derived from work by Gary S. Brown. + */ + +#include +#include + +static uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +#define DO1(buf) crc = crc32_tab[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8); +#define DO2(buf) DO1(buf); DO1(buf); +#define DO4(buf) DO2(buf); DO2(buf); +#define DO8(buf) DO4(buf); DO4(buf); + +uint32_t +crc32(uint32_t crc, const void *ubuf, size_t len) +{ + const uint8_t *buf = ubuf; + + crc = crc ^ ~0U; + + while (len >= 8) { + DO8(buf); + len -= 8; + } + while (len) { + DO1(buf); + len--; + } + + return crc ^ ~0U; +} diff --git a/crc32.h b/crc32.h new file mode 100644 index 0000000..e06922e --- /dev/null +++ b/crc32.h @@ -0,0 +1,8 @@ +#ifndef CRC32_H +#define CRC32_H + +#include + +uint32_t crc32(uint32_t crc, const void *buf, size_t len); + +#endif diff --git a/gpt.c b/gpt.c new file mode 100644 index 0000000..d5953c6 --- /dev/null +++ b/gpt.c @@ -0,0 +1,735 @@ +#define _LARGEFILE64_SOURCE + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "gpt.h" + +#include "util.h" +#include "crc32.h" + +#define DEFAULT_LBSIZE 512 +#define MIN_LBSIZE 512 +#define MAX_LBSIZE 4096 + +#if defined(ANDROID) && defined(QCOM) +#define IS_PART_RO(gpt,n) ((n) <= (gpt->pad_idx)) +#else +#define IS_PART_RO(gpt,n) (0) +#endif + +static const byte gpt_header_signature[8] = { + 0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54 +}; + +static void u16_to_ascii(const byte *u, char *s) +{ + uint32_t n; + for (n = 0; n < 72/2; ++n) { + s[n] = u[n*2]; + } + s[72/2] = '\0'; +} + +static void guid_to_ascii(const byte *guid, char *s) +{ + uint32_t p1; + uint16_t p2; + uint16_t p3; + unsigned char p4[8]; + + memcpy(&p1, guid + 0, 4); + memcpy(&p2, guid + 4, 2); + memcpy(&p3, guid + 6, 2); + memcpy(p4, guid + 8, 8); + + sprintf(s, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + p1, p2, p3, p4[0], p4[1], + p4[2], p4[3], p4[4], p4[5], p4[6], p4[7]); +} + +static void gpt_header_show(const char *msg, const struct gpt_header *header) +{ + char guidstr[80]; + + guid_to_ascii(header->disk_guid, guidstr); + + printf("%s:" + " size=%lu\n" + " current_lba=%llu\n" + " backup_lba=%llu\n" + " first_usable_lba=%llu\n" + " last_usable_lba=%llu\n" + " guid=%s\n" + " ptbl_lba=%llu\n" + " ptbl_count=%lu\n" + " ptbl_entry_size=%lu\n", + msg, + (unsigned long)header->size, + (unsigned long long)header->current_lba, + (unsigned long long)header->backup_lba, + (unsigned long long)header->first_usable_lba, + (unsigned long long)header->last_usable_lba, + guidstr, + (unsigned long long)header->ptbl_lba, + (unsigned long)header->ptbl_count, + (unsigned long)header->ptbl_entry_size); +} + +static void gpt_part_show(uint32_t idx, const struct gpt_partition *part) +{ + char name[72+1]; + uint64_t start, end, size; + + u16_to_ascii(part->name, name); + start = part->first_lba; + end = part->last_lba; + size = end - start + 1; + printf(" p%-2u: [%8llu..%8llu] size=%8llu name=%s\n", + idx, + (unsigned long long)start, + (unsigned long long)end, + (unsigned long long)size, + name); +} + +static int gpt_header_is_valid(struct gpt_header *header, uint32_t lbsize) +{ + uint32_t read_crc, calc_crc; + + if (memcmp(header->signature, gpt_header_signature, sizeof(gpt_header_signature)) != 0) { + return -1; + } + if (header->size < GPT_HDR_SIZE || header->size > lbsize) { + return -1; + } + if (header->revision != 0x00010000) { + return -1; + } + read_crc = header->crc; + header->crc = 0; + calc_crc = crc32(0, header, header->size); + if (read_crc != calc_crc) { + return -1; + } + + if (header->ptbl_count < GPT_MIN_PARTITIONS || header->ptbl_count > GPT_MAX_PARTITIONS) { + return -1; + } + + if (header->ptbl_entry_size < GPT_PART_SIZE || header->ptbl_entry_size > MAX_LBSIZE) { + return -1; + } + + return 0; +} + +int gpt_open(struct gpt *gpt, const char *dev) +{ + int rc; + off64_t off; + byte buf[MAX_LBSIZE]; + uint32_t calc_crc; + uint64_t next_lba; + uint32_t n; + struct stat st; + int primary_header_valid = 0; + int backup_header_valid = 0; + + memset(gpt, 0, sizeof(struct gpt)); + + gpt->fd = open(dev, O_RDWR); + if (gpt->fd < 0) { + perror("open"); + return -1; + } + + rc = fstat(gpt->fd, &st); + if (rc != 0) { + perror("fstat"); + return -1; + } + + gpt->lbsize = DEFAULT_LBSIZE; + if (S_ISBLK(st.st_mode)) { + unsigned long blklen; + /* XXX: Linux LB size is always 512? */ + if (ioctl(gpt->fd, BLKGETSIZE, &blklen, sizeof(blklen)) == 0) { + gpt->lblen = blklen; + } + } + + off = lseek64(gpt->fd, 1*gpt->lbsize, SEEK_SET); + if (off < 0) { + perror("lseek"); + close(gpt->fd); + return -1; + } + rc = read(gpt->fd, buf, gpt->lbsize); + if (rc != (ssize_t)gpt->lbsize) { + fprintf(stderr, "bad read\n"); + close(gpt->fd); + return -1; + } + memcpy(&gpt->header, buf, sizeof(struct gpt_header)); + if (gpt_header_is_valid(&gpt->header, gpt->lbsize) != 0) { + fprintf(stderr, "bad gpt header\n"); + close(gpt->fd); + return -1; + } +#if defined(ANDROID) && defined(QCOM) + if (gpt->header.current_lba != 1 || + gpt->header.backup_lba != gpt->lblen - 1 || + gpt->header.first_usable_lba != 34 || + gpt->header.last_usable_lba <= gpt->header.first_usable_lba || + gpt->header.last_usable_lba >= gpt->lblen - 1 || + gpt->header.ptbl_lba != 2 || + gpt->header.ptbl_count < GPT_MIN_PARTITIONS || + gpt->header.ptbl_count > GPT_MAX_PARTITIONS || + gpt->header.ptbl_entry_size > gpt->lbsize) { + fprintf(stderr, "W: bad primary gpt\n"); + goto primary_header_out; + } +#else + if (gpt->header.current_lba != 1 || + gpt->header.backup_lba >= gpt->lblen || + gpt->header.first_usable_lba < 2 || + gpt->header.first_usable_lba >= gpt->lblen || + gpt->header.last_usable_lba <= gpt->header.first_usable_lba || + gpt->header.last_usable_lba >= gpt->lblen || + gpt->header.ptbl_lba >= gpt->lblen || + gpt->header.ptbl_count < GPT_MIN_PARTITIONS || + gpt->header.ptbl_count > GPT_MAX_PARTITIONS || + gpt->header.ptbl_entry_size > gpt->lbsize) { + fprintf(stderr, "W: bad primary gpt\n"); + goto primary_header_out; + } +#endif + primary_header_valid = 1; +primary_header_out: + + /* Validate backup GPT for block devices */ + if (S_ISBLK(st.st_mode) && + gpt->header.backup_lba > 2 && + gpt->header.backup_lba < gpt->lblen) { + off = lseek64(gpt->fd, gpt->header.backup_lba * gpt->lbsize, SEEK_SET); + if (off < 0) { + fprintf(stderr, "bad backup seek\n"); + goto backup_header_out; + } + rc = read(gpt->fd, buf, gpt->lbsize); + if (rc != (ssize_t)gpt->lbsize) { + fprintf(stderr, "bad backup read\n"); + goto backup_header_out; + } + memcpy(&gpt->backup_header, buf, sizeof(struct gpt_header)); + if (gpt_header_is_valid(&gpt->backup_header, gpt->lbsize) != 0) { + fprintf(stderr, "bad backup header\n"); + goto backup_header_out; + } +#if defined(ANDROID) && defined(QCOM) + if (gpt->backup_header.current_lba != gpt->header.backup_lba || + gpt->backup_header.backup_lba != 1 || + gpt->backup_header.first_usable_lba != gpt->header.first_usable_lba || + gpt->backup_header.last_usable_lba != gpt->header.last_usable_lba || + memcmp(gpt->backup_header.disk_guid, gpt->header.disk_guid, 16) != 0 || + gpt->backup_header.ptbl_lba >= gpt->lblen || + gpt->backup_header.ptbl_count != gpt->header.ptbl_count || + gpt->backup_header.ptbl_entry_size != gpt->header.ptbl_entry_size) { + fprintf(stderr, "W: bad backup gpt\n"); + goto backup_header_out; + } +#else + if (gpt->backup_header.current_lba != gpt->header.backup_lba || + gpt->backup_header.backup_lba != 1 || + gpt->backup_header.first_usable_lba != gpt->header.first_usable_lba || + gpt->backup_header.last_usable_lba != gpt->header.last_usable_lba || + memcmp(gpt->backup_header.disk_guid, gpt->header.disk_guid, 16) != 0 || + gpt->backup_header.ptbl_lba >= gpt->lblen || + gpt->backup_header.ptbl_count != gpt->header.ptbl_count || + gpt->backup_header.ptbl_entry_size != gpt->header.ptbl_entry_size) { + fprintf(stderr, "W: bad backup gpt\n"); + goto backup_header_out; + } +#endif + backup_header_valid = 1; + } +backup_header_out: + + next_lba = gpt->header.first_usable_lba; + + if (primary_header_valid) { + off = lseek64(gpt->fd, gpt->header.ptbl_lba * gpt->lbsize, SEEK_SET); + if (off < 0) { + perror("lseek\n"); + close(gpt->fd); + return -1; + } + + calc_crc = 0; + for (n = 0; n < gpt->header.ptbl_count; ++n) { + gpt->partitions[n] = (struct gpt_partition *)malloc(sizeof(struct gpt_partition)); + memset(gpt->partitions[n], 0, sizeof(struct gpt_partition)); + rc = read(gpt->fd, gpt->partitions[n], gpt->header.ptbl_entry_size); + if (rc < 0 || (uint32_t)rc != gpt->header.ptbl_entry_size) { + fprintf(stderr, "failed to read partition entry %u\n", n); + close(gpt->fd); + return -1; + } + calc_crc = crc32(calc_crc, gpt->partitions[n], gpt->header.ptbl_entry_size); + if (gpt->partitions[n]->first_lba == 0 && gpt->partitions[n]->last_lba == 0) { + continue; + } + if (gpt->partitions[n]->first_lba < next_lba || + gpt->partitions[n]->last_lba < gpt->partitions[n]->first_lba || + gpt->partitions[n]->last_lba > gpt->header.last_usable_lba) { + fprintf(stderr, "bad lba in partition entry %u\n", n); + close(gpt->fd); + return -1; + } + gpt->last_used_idx = n; + } + + if (gpt->header.ptbl_crc != calc_crc) { + fprintf(stderr, "bad ptbl crc\n"); + close(gpt->fd); + return -1; + } + } + + if (backup_header_valid) { + int warned = 0; + off = lseek64(gpt->fd, gpt->backup_header.ptbl_lba * gpt->lbsize, SEEK_SET); + if (off < 0) { + perror("lseek\n"); + close(gpt->fd); + return -1; + } + calc_crc = 0; + for (n = 0; n < gpt->backup_header.ptbl_count; ++n) { + struct gpt_partition backup_part; + rc = read(gpt->fd, &backup_part, gpt->backup_header.ptbl_entry_size); + if (rc < 0 || (uint32_t)rc != gpt->backup_header.ptbl_entry_size) { + fprintf(stderr, "failed to read backup partition entry %u\n", n); + close(gpt->fd); + return -1; + } + if (memcmp(gpt->partitions[n], &backup_part, sizeof(struct gpt_partition)) != 0) { + if (!warned) { + fprintf(stderr, "mismatched backup partition entry %u\n", n); + warned = 1; + } + } + } + } + +#if defined(ANDROID) && defined(QCOM) + gpt->pad_idx = gpt_part_find(gpt, "pad"); + if (gpt->pad_idx == GPT_PART_INVALID) { + fprintf(stderr, "no pad found\n"); + close(gpt->fd); + return -1; + } +#endif + + return 0; +} + +int gpt_write(const struct gpt *gpt) +{ + int rc; + off64_t off; + struct gpt_header hdr; + uint32_t n; + + memcpy(&hdr, &gpt->header, sizeof(hdr)); + + hdr.ptbl_crc = 0; + for (n = 0; n < gpt->header.ptbl_count; ++n) { + hdr.ptbl_crc = crc32(hdr.ptbl_crc, gpt->partitions[n], gpt->header.ptbl_entry_size); + } + + hdr.crc = 0; + hdr.crc = crc32(0, &hdr, hdr.size); + + off = lseek64(gpt->fd, hdr.current_lba * gpt->lbsize, SEEK_SET); + if (off < 0) { + perror("lseek"); + return -1; + } + rc = write(gpt->fd, &hdr, hdr.size); + if (rc < 0 || (uint32_t)rc != hdr.size) { + fprintf(stderr, "bad primary header write\n"); + return -1; + } + + off = lseek64(gpt->fd, hdr.ptbl_lba * gpt->lbsize, SEEK_SET); + if (off < 0) { + perror("lseek\n"); + return -1; + } + + for (n = 0; n < hdr.ptbl_count; ++n) { + rc = write(gpt->fd, gpt->partitions[n], hdr.ptbl_entry_size); + if (rc < 0 || (uint32_t)rc != hdr.ptbl_entry_size) { + fprintf(stderr, "bad primary partition write\n"); + return -1; + } + } + + hdr.current_lba = gpt->header.backup_lba; + hdr.backup_lba = 1; + hdr.ptbl_lba = gpt->lblen - + ROUNDUP(hdr.ptbl_count * hdr.ptbl_entry_size, gpt->lbsize) / gpt->lbsize - 1; + + hdr.crc = 0; + hdr.crc = crc32(0, &hdr, hdr.size); + + off = lseek64(gpt->fd, hdr.current_lba * gpt->lbsize, SEEK_SET); + if (off < 0) { + perror("lseek"); + return -1; + } + rc = write(gpt->fd, &hdr, hdr.size); + if (rc < 0 || (uint32_t)rc != hdr.size) { + fprintf(stderr, "bad backup header write\n"); + return -1; + } + + off = lseek64(gpt->fd, hdr.ptbl_lba * gpt->lbsize, SEEK_SET); + if (off < 0) { + perror("lseek\n"); + return -1; + } + + for (n = 0; n < hdr.ptbl_count; ++n) { + rc = write(gpt->fd, gpt->partitions[n], hdr.ptbl_entry_size); + if (rc < 0 || (uint32_t)rc != hdr.ptbl_entry_size) { + fprintf(stderr, "bad backup partition write\n"); + return -1; + } + } + + return 0; +} + +int gpt_close(struct gpt *gpt) +{ + int rc; + + rc = close(gpt->fd); + gpt->fd = -1; + + return rc; +} + +void gpt_show(const struct gpt *gpt) +{ + uint32_t n; + + gpt_header_show("Primary GPT", &gpt->header); + + if (gpt->backup_header.size != 0) { + gpt_header_show("Backup GPT", &gpt->backup_header); + } + + printf("Partition table: count=%u\n", (unsigned int)gpt->last_used_idx); + for (n = 0; n < gpt->header.ptbl_count; ++n) { + if (gpt->partitions[n]->first_lba == 0 && gpt->partitions[n]->last_lba == 0) { + continue; + } + gpt_part_show(n, gpt->partitions[n]); + } +} + +uint32_t gpt_part_find(const struct gpt *gpt, const char *name) +{ + uint32_t n; + char curname[72/2+1]; + + for (n = 0; n < gpt->header.ptbl_count; ++n) { + u16_to_ascii(gpt->partitions[n]->name, curname); + if (!strcmp(name, curname)) { + return n; + } + } + + return GPT_PART_INVALID; +} + +int gpt_part_name(const struct gpt *gpt, uint32_t idx, char *name) +{ + u16_to_ascii(gpt->partitions[idx]->name, name); + return 0; +} + +uint64_t gpt_part_size(const struct gpt *gpt, uint32_t idx) +{ + uint64_t lbsize; + + lbsize = gpt->partitions[idx]->last_lba - gpt->partitions[idx]->first_lba + 1; + return (lbsize * gpt->lbsize); +} + +int gpt_part_add(struct gpt *gpt, uint32_t idx, const struct gpt_partition *part, int follow) +{ + uint64_t lba_min, lba_max; + uint32_t n; + + if (IS_PART_RO(gpt, idx) || idx > gpt->last_used_idx+1 || + gpt->last_used_idx >= gpt->header.ptbl_count) { + return -1; + } + + lba_min = (idx == 0 ? + gpt->header.first_usable_lba : + gpt->partitions[idx-1]->last_lba + 1); + lba_max = (idx == gpt->header.ptbl_count ? + gpt->header.last_usable_lba : + gpt->partitions[idx]->first_lba - 1); + + if (part->first_lba < lba_min || part->last_lba > lba_max) { + return -1; + } + + for (n = gpt->header.ptbl_count; n > idx; --n) { + gpt->partitions[n] = gpt->partitions[n-1]; + } + gpt->partitions[n] = (struct gpt_partition *)malloc(sizeof(struct gpt_partition)); + memcpy(gpt->partitions[n], part, sizeof(struct gpt_partition)); + + gpt->last_used_idx++; + + return 0; +} + +int gpt_part_del(struct gpt *gpt, uint32_t idx, int follow) +{ + uint32_t n; + uint64_t len, delta; + + if (IS_PART_RO(gpt, idx) || idx > gpt->last_used_idx) { + return -1; + } + + len = gpt->partitions[idx]->last_lba - gpt->partitions[idx]->first_lba + 1; + delta = 0 - len; + + for (n = idx; n < gpt->last_used_idx; ++n) { + gpt->partitions[n] = gpt->partitions[n+1]; + if (follow) { + gpt->partitions[n]->first_lba += delta; + gpt->partitions[n]->last_lba += delta; + } + } + + gpt->last_used_idx--; + + return 0; +} + +int gpt_part_move(struct gpt *gpt, uint32_t idx, uint64_t lba, int follow) +{ + uint32_t n; + int64_t lbdelta; + + if (IS_PART_RO(gpt, idx) || idx > gpt->last_used_idx) { + return -1; + } + + lbdelta = (int64_t)lba - (int64_t)gpt->partitions[idx]->first_lba; + if (lbdelta == 0) { + return 0; + } + + if (lbdelta < 0) { + /* + * Figure out the minimum lba for the partition. For now, we will + * just use the end of the previous partition. Later, we could + * try to do something like "follow" in reverse. + */ + uint64_t lba_min = gpt->partitions[idx-1]->last_lba + 1; + if (lba < lba_min) { + return -1; + } + } + else { + /* + * Figure out the maximum lba delta: + * - For the last partition, limit is last usable LBA less end LBA. + * - If follow is set, limit is same as last partition. + * - If follow is unset, limit is start of next partition less this partition size. + */ + uint64_t lbamaxdelta; + if (idx == gpt->last_used_idx || follow) { + uint32_t lui = gpt->last_used_idx; + lbamaxdelta = gpt->header.last_usable_lba - gpt->partitions[lui]->last_lba; + } + else { + lbamaxdelta = gpt->partitions[idx+1]->first_lba - gpt->partitions[idx]->last_lba - 1; + } + if (lbdelta > (int64_t)lbamaxdelta) { + return -1; + } + } + + gpt->partitions[idx]->first_lba += lbdelta; + gpt->partitions[idx]->last_lba += lbdelta; + + if (follow) { + for (n = idx+1; n <= gpt->header.ptbl_count; ++n) { + gpt->partitions[n]->first_lba += lbdelta; + gpt->partitions[n]->last_lba += lbdelta; + } + } + + return 0; +} + +int gpt_part_resize(struct gpt *gpt, uint32_t idx, uint64_t size, int follow) +{ + uint32_t n; + int64_t lbdelta; + + if (IS_PART_RO(gpt, idx) || idx > gpt->last_used_idx) { + return -1; + } + if (size & (gpt->lbsize - 1)) { + return -1; + } + lbdelta = ((int64_t)size - (int64_t)gpt_part_size(gpt, idx)) / gpt->lbsize; + if (lbdelta == 0) { + return 0; + } + + if (lbdelta > 0) { + /* + * Figure out how much the partition may expand: + * - For the last partition, limit is last usable LBA. + * - If follow is set, limit is same as last partition. + * - If follow is unset, limit is start of next partition. + */ + uint64_t lbamaxdelta; + if (idx == gpt->last_used_idx || follow) { + uint32_t lui = gpt->last_used_idx; + lbamaxdelta = gpt->header.last_usable_lba - gpt->partitions[lui]->last_lba; + } + else { + lbamaxdelta = gpt->partitions[idx+1]->first_lba - gpt->partitions[idx]->last_lba - 1; + } + if (lbdelta > (int64_t)lbamaxdelta) { + return -1; + } + } + + gpt->partitions[idx]->last_lba += lbdelta; + + if (follow) { + for (n = idx+1; n <= gpt->last_used_idx; ++n) { + gpt->partitions[n]->first_lba += lbdelta; + gpt->partitions[n]->last_lba += lbdelta; + } + } + + return 0; +} + +int gpt_part_save(struct gpt *gpt, uint32_t idx, const char *filename) +{ + uint64_t remain; + int fd; + char buf[4096]; + + if (lseek64(gpt->fd, + gpt->partitions[idx]->first_lba * gpt->lbsize, + SEEK_SET) < 0) { + return -1; + } + remain = gpt_part_size(gpt, idx); + + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) { + return -1; + } + + while (remain > 0) { + size_t toread; + ssize_t nread, nwritten; + toread = (remain > sizeof(buf) ? sizeof(buf) : remain); + nread = read(gpt->fd, buf, toread); + if (nread <= 0) { + goto out_err; + } + nwritten = write(fd, buf, nread); + if (nwritten != nread) { + goto out_err; + } + remain -= nread; + } + close(fd); + + return 0; + +out_err: + close(fd); + return -1; +} + +int gpt_part_load(struct gpt *gpt, uint32_t idx, const char *filename) +{ + uint64_t remain; + int fd; + struct stat st; + char buf[4096]; + + if (lseek64(gpt->fd, + gpt->partitions[idx]->first_lba * gpt->lbsize, + SEEK_SET) < 0) { + return -1; + } + remain = gpt_part_size(gpt, idx); + + fd = open(filename, O_RDONLY); + if (fd < 0) { + return -1; + } + if (fstat(fd, &st) != 0) { + perror("fstat"); + goto out_err; + } + if ((uint64_t)st.st_size != gpt_part_size(gpt, idx)) { + fprintf(stderr, "E: file %s has incorrect size\n", filename); + goto out_err; + } + + while (remain > 0) { + size_t toread; + ssize_t nread, nwritten; + toread = (remain > sizeof(buf) ? sizeof(buf) : remain); + nread = read(fd, buf, toread); + if (nread <= 0) { + goto out_err; + } + nwritten = write(gpt->fd, buf, nread); + if (nwritten != nread) { + goto out_err; + } + remain -= nread; + } + close(fd); + + return 0; + +out_err: + close(fd); + return -1; +} diff --git a/gpt.h b/gpt.h new file mode 100644 index 0000000..f72223e --- /dev/null +++ b/gpt.h @@ -0,0 +1,164 @@ +#ifndef GPT_H +#define GPT_H + +#include "types.h" + +#define GPT_HDR_SIZE 92 +struct gpt_header +{ + byte signature[8]; + uint32_t revision; + uint32_t size; + uint32_t crc; + uint32_t reserved; + uint64_t current_lba; + uint64_t backup_lba; + uint64_t first_usable_lba; + uint64_t last_usable_lba; + byte disk_guid[16]; + uint64_t ptbl_lba; + uint32_t ptbl_count; + uint32_t ptbl_entry_size; + uint32_t ptbl_crc; + byte padding[512-GPT_HDR_SIZE]; +} __attribute__((packed)); + +#define GPT_PART_SIZE 128 +struct gpt_partition +{ + byte type_guid[16]; + byte part_guid[16]; + uint64_t first_lba; + uint64_t last_lba; + byte flags[8]; + byte name[72]; + byte padding[512-GPT_PART_SIZE]; +} __attribute__((packed)); + +#if defined(ANDROID) && defined(QCOM) +#define GPT_MIN_PARTITIONS 8 +#define GPT_MAX_PARTITIONS 128 +#else +#define GPT_MIN_PARTITIONS 4 +#define GPT_MAX_PARTITIONS 256 +#endif + +#define GPT_PART_INVALID (uint32_t)(~0) + +struct gpt +{ + int fd; + uint32_t lbsize; + uint32_t lblen; + struct gpt_header header; + struct gpt_header backup_header; +#if defined(ANDROID) && defined(QCOM) + uint32_t pad_idx; +#endif + uint32_t last_used_idx; + struct gpt_partition* partitions[GPT_MAX_PARTITIONS]; +}; + +int gpt_open(struct gpt *gpt, const char *dev); +int gpt_write(const struct gpt *gpt); +int gpt_close(struct gpt *gpt); +void gpt_show(const struct gpt *gpt); + +/* + * gpt_part_find: Find a partition + * name: ASCII encoded name to find. + * Returns index if found. + * Returns GPT_PART_INVALID on error. + */ +uint32_t gpt_part_find(const struct gpt *gpt, const char *name); + +#define GPT_PART_NAMELEN (72/2+1) +int gpt_part_name(const struct gpt *gpt, uint32_t idx, char *name); +uint64_t gpt_part_size(const struct gpt *gpt, uint32_t idx); + +/* + * gpt_part_add: Add a partititon + * idx : Index of partition. + * part : Partition data to add. + * follow: Whether to follow/expand. + * + * If follow is zero, the partition at 'idx' must be empty, and there must be + * enough room to insert it (either it must be the last entry, or the + * following entry must start after the end of the new entry). + * + * If follow is nonzero, existing partitions starting at 'idx' are pushed up + * to make room for the new partition. The total partition table must not + * expand past the usable lba space. + * + * Returns zero if successful, nonzero on error. + */ +int gpt_part_add(struct gpt *gpt, uint32_t idx, const struct gpt_partition *part, int follow); + +/* + * gpt_part_del: Delete a partititon + * idx : Index of partition. + * follow: Whether to follow/contract. + * + * If follow is zero, the partition at 'idx' is zeroed. + * + * If follow is nonzero, existing partitions starting at 'idx' are pushed down + * to fill in the unused space. + * + * Returns zero if successful, nonzero on error. + */ +int gpt_part_del(struct gpt *gpt, uint32_t idx, int follow); + +/* + * gpt_part_move: Move a partititon + * idx : Index of partition. + * lba : New starting LBA. + * follow: Whether to follow. + * + * If follow is zero, the partition is moved and no other action is taken. + * The destination must be within the current allowed space. + * + * If follow is nonzero, existing partitions starting at 'idx+1' are moved + * in the same amount (up or down) as the specified partition. The + * destination must be above the previous partititon and the total partition + * table must not expand past the usable space. + * + * Returns zero if successful, nonzero on error. + */ +int gpt_part_move(struct gpt *gpt, uint32_t idx, uint64_t lba, int follow); + +/* + * gpt_part_resize: Resize a partititon + * idx : Index of partition. + * size : New size. + * follow: Whether to follow. + * + * If follow is zero, the partition is resized and no other action is taken. + * The destination must be within the current allowed space. + * + * If follow is nonzero, existing partitions starting at 'idx+1' are moved + * in the same amount (up or down) as the specified partition delta. The + * total partition table must not expand past the usable space. + * + * Returns zero if successful, nonzero on error. + */ +int gpt_part_resize(struct gpt *gpt, uint32_t idx, uint64_t size, int follow); + +/* + * gpt_part_save: Save a partition to a file + * idx : Index of partition. + * filename: Filename to save data. + * + * Returns zero if successful, nonzero on error. + */ +int gpt_part_save(struct gpt *gpt, uint32_t idx, const char *filename); + +/* + * gpt_part_load: Load a partition from a file + * idx : Index of partition. + * filename: Filename to load data. + * + * Returns zero if successful, nonzero on error. + */ +int gpt_part_load(struct gpt *gpt, uint32_t idx, const char *filename); + +#endif diff --git a/gpted.c b/gpted.c new file mode 100644 index 0000000..1866afa --- /dev/null +++ b/gpted.c @@ -0,0 +1,356 @@ +#include +#include +#include + +#include +#include + +#include +#include + +#include "util.h" + +#include "gpt.h" + +#define MAX_ARGS 8 + +static int cmd_help(struct gpt *gpt, unsigned int argc, const char **argv) +{ + return 0; +} + +static int cmd_quit(struct gpt *gpt, unsigned int argc, const char **argv) +{ + return 1; +} + +static int cmd_show(struct gpt *gpt, unsigned int argc, const char **argv) +{ + gpt_show(gpt); + return 0; +} + +static int cmd_write(struct gpt *gpt, unsigned int argc, const char **argv) +{ + int rc; + + rc = gpt_write(gpt); + if (rc != 0) { + printf("E: write failed\n"); + return 0; + } + + return 0; +} + +#if defined(ANDROID) && defined(QCOM) +static const char *non_firmware[] = { + "recovery", "boot", + "system", "userdata", "cache", "sdcard", + /* persist? */ + NULL +}; + +static int cmd_firmware_save(struct gpt *gpt, unsigned int argc, const char **argv) +{ + int all = 0; + uint32_t startidx, idx; + + if (argc > 1) { + all = !strcmp(argv[1], "all"); + } + + startidx = (all ? 0 : gpt->pad_idx+1); + for (idx = startidx; idx <= gpt->last_used_idx; ++idx) { + char name[72/2+1]; + char filename[72/2+4+1]; + int skip = 0; + const char **entry; + + gpt_part_name(gpt, idx, name); + for (entry = non_firmware; *entry; ++entry) { + if (!strcmp(name, *entry)) { + skip = 1; + } + } + if (skip) { + printf("Skip %s\n", name); + } + else { + printf("Save %s\n", name); + sprintf(filename, "%s.img", name); + gpt_part_save(gpt, idx, filename); + } + } + + return 0; +} + +static int cmd_firmware_load(struct gpt *gpt, unsigned int argc, const char **argv) +{ + int all = 0; + uint32_t startidx, idx; + + if (argc > 1) { + all = !strcmp(argv[1], "all"); + } + + startidx = (all ? 0 : gpt->pad_idx+1); + for (idx = startidx; idx <= gpt->last_used_idx; ++idx) { + char name[72/2+1]; + char filename[72/2+4+1]; + int skip = 0; + const char **entry; + + gpt_part_name(gpt, idx, name); + for (entry = non_firmware; *entry; ++entry) { + if (!strcmp(name, *entry)) { + skip = 1; + } + } + if (skip) { + printf("Skip %s\n", name); + } + else { + printf("Load %s\n", name); + sprintf(filename, "%s.img", name); + gpt_part_load(gpt, idx, filename); + } + } + + return 0; +} +#endif + +static int cmd_part_add(struct gpt *gpt, unsigned int argc, const char **argv) +{ + printf("E: not implemented\n"); + return 0; +} + +static int cmd_part_del(struct gpt *gpt, unsigned int argc, const char **argv) +{ + int rc; + uint32_t idx; + int follow; + + if (argc < 2) { + printf("E: not enough args\n"); + return 0; + } + idx = gpt_part_find(gpt, argv[1]); + if (idx == GPT_PART_INVALID) { + printf("E: part %s not found\n", argv[1]); + return 0; + } + follow = (argc > 2 && !strcmp(argv[2], "follow")); + rc = gpt_part_del(gpt, idx, follow); + if (rc != 0) { + printf("E: failed\n"); + return 0; + } + return 0; +} + +static int cmd_part_move(struct gpt *gpt, unsigned int argc, const char **argv) +{ + int rc; + uint32_t idx; + uint64_t lb; + int follow; + + if (argc < 3) { + printf("E: not enough args\n"); + return 0; + } + idx = gpt_part_find(gpt, argv[1]); + if (idx == GPT_PART_INVALID) { + printf("E: part %s not found\n", argv[1]); + return 0; + } + lb = strtoull_u(argv[2], NULL, 0); + follow = (argc > 3 && !strcmp(argv[3], "follow")); + rc = gpt_part_move(gpt, idx, lb, follow); + if (rc != 0) { + printf("E: failed\n"); + return 0; + } + + return 0; +} + +static int cmd_part_resize(struct gpt *gpt, unsigned int argc, const char **argv) +{ + int rc; + uint32_t idx; + uint64_t size; + int follow; + + if (argc < 3) { + printf("E: not enough args\n"); + return 0; + } + idx = gpt_part_find(gpt, argv[1]); + if (idx == GPT_PART_INVALID) { + printf("E: part %s not found\n", argv[1]); + return 0; + } + follow = (argc > 3 && !strcmp(argv[3], "follow")); + if (!strcmp(argv[2], "max")) { + if (follow) { + struct gpt_partition *endpart; + endpart = gpt->partitions[gpt->last_used_idx]; + size = gpt_part_size(gpt, idx) + + (gpt->header.last_usable_lba - endpart->last_lba) * gpt->lbsize; + } + else { + size = strtoull_u(argv[2], NULL, 0); + } + } + else { + size = strtoull_u(argv[2], NULL, 0); + } + rc = gpt_part_resize(gpt, idx, size, follow); + if (rc != 0) { + printf("E: failed\n"); + return 0; + } + + return 0; +} + +static int cmd_part_load(struct gpt *gpt, unsigned int argc, const char **argv) +{ + int rc; + uint32_t idx; + + if (argc < 3) { + printf("E: not enough args\n"); + return 0; + } + idx = gpt_part_find(gpt, argv[1]); + rc = gpt_part_load(gpt, idx, argv[2]); + if (rc != 0) { + printf("E: failed\n"); + return 0; + } + + return 0; +} + +static int cmd_part_save(struct gpt *gpt, unsigned int argc, const char **argv) +{ + int rc; + uint32_t idx; + + if (argc < 3) { + printf("E: not enough args\n"); + return 0; + } + idx = gpt_part_find(gpt, argv[1]); + rc = gpt_part_save(gpt, idx, argv[2]); + if (rc != 0) { + printf("E: failed\n"); + return 0; + } + + return 0; +} + +struct dispatch_entry +{ + const char *cmd; + int (*func)(struct gpt *gpt, unsigned int argc, const char **argv); +}; + +static struct dispatch_entry dispatch_table[] = { + { "help", cmd_help }, + { "quit", cmd_quit }, + { "show", cmd_show }, + { "write", cmd_write }, + +#if defined(ANDROID) && defined(QCOM) + { "firmware-save", cmd_firmware_save }, + { "firmware-load", cmd_firmware_load }, +#endif + + { "part-add", cmd_part_add }, + { "part-del", cmd_part_del }, + { "part-move", cmd_part_move }, + { "part-resize", cmd_part_resize }, + { "part-load", cmd_part_load }, + { "part-save", cmd_part_save }, + + { NULL, NULL } +}; + +static int dispatch(struct gpt *gpt, char *line) +{ + char *p; + unsigned int argc; + const char *argv[MAX_ARGS]; + struct dispatch_entry *entry; + + while (*line == ' ') { + ++line; + } + if (*line == '#' || *line == ';') { + return 0; + } + + argc = 0; + p = strtok(line, " "); + while (p != NULL) { + argv[argc++] = p; + p = strtok(NULL, " "); + } + if (argc == 0) { + return 0; + } + + for (entry = dispatch_table; entry->cmd != NULL; ++entry) { + if (!strcmp(argv[0], entry->cmd)) { + return entry->func(gpt, argc, argv); + } + } + + printf("Unknown command %s\n", argv[0]); + return 0; +} + +int main(int argc, char** argv) +{ + int rc; + struct gpt gpt; + const char* dev; + const char *prompt; + char *line; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + dev = argv[1]; + rc = gpt_open(&gpt, dev); + if (rc != 0) { + fprintf(stderr, "Failed to read gpt\n"); + exit(1); + } + + prompt = (isatty(STDIN_FILENO) ? "partedit> " : NULL); + while ((line = readline(prompt)) != NULL) { + rc = dispatch(&gpt, line); + free(line); + if (rc != 0) { + break; + } + } + if (prompt) { + printf("\n"); + } + + gpt_close(&gpt); + + return 0; +} diff --git a/readline/history.h b/readline/history.h new file mode 100644 index 0000000..e909ff6 --- /dev/null +++ b/readline/history.h @@ -0,0 +1,6 @@ +#ifndef HISTORY_H +#define HISTORY_H + +/* empty */ + +#endif diff --git a/readline/readline.c b/readline/readline.c new file mode 100644 index 0000000..201bea6 --- /dev/null +++ b/readline/readline.c @@ -0,0 +1,29 @@ +#include +#include +#include + +#include "readline.h" + +char *readline(const char *prompt) +{ + char *buf = NULL; + size_t n = 0; + ssize_t len; + + if (prompt && *prompt) { + fprintf(stdout, "%s", prompt); + fflush(stdout); + } + len = getline(&buf, &n, stdin); + if (len == -1) { + free(buf); + buf = NULL; + } + else { + char *p = strchr(buf, '\n'); + if (p) + *p = '\0'; + } + + return buf; +} diff --git a/readline/readline.h b/readline/readline.h new file mode 100644 index 0000000..c8489fd --- /dev/null +++ b/readline/readline.h @@ -0,0 +1,6 @@ +#ifndef READLINE_H +#define READLINE_H + +extern char *readline(const char *prompt); + +#endif diff --git a/types.h b/types.h new file mode 100644 index 0000000..60266c3 --- /dev/null +++ b/types.h @@ -0,0 +1,8 @@ +#ifndef TYPES_H +#define TYPES_H + +#include + +typedef unsigned char byte; + +#endif diff --git a/util.c b/util.c new file mode 100644 index 0000000..4ab6c4c --- /dev/null +++ b/util.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include + +#include "util.h" + +void hexdump(const byte *buf, uint32_t len) +{ + uint32_t x, y; + for (y = 0; y*16 < len; ++y) { + for (x = 0; x < 16 && y*16+x < len; ++x) { + printf("%02x ", buf[y*16+x]); + } + printf("\n"); + } +} + +unsigned int user_multiplier(char **p) +{ + char c = **p; + switch (c) + { + case 's': case 'S': + ++(*p); + return 512; + case 'k': case 'K': + ++(*p); + return 1024; + case 'm': case 'M': + ++(*p); + return 1024*1024; + case 'g': case 'G': + ++(*p); + return 1024*1024*1024; + default: + break; + } + return 1; +} + +unsigned long int strtoul_u(const char *p, char **endp, int base) +{ + char *u_endp; + unsigned long int val; + unsigned int mul; + val = strtoul(p, &u_endp, base); + if (val == ULONG_MAX || errno == ERANGE) { + return val; + } + mul = user_multiplier(&u_endp); + if (endp) + *endp = u_endp; + return val * mul; +} + +unsigned long long int strtoull_u(const char *p, char **endp, int base) +{ + char *u_endp; + unsigned long long int val; + unsigned int mul; + val = strtoull(p, &u_endp, base); + if (val == ULLONG_MAX || errno == ERANGE) { + return val; + } + mul = user_multiplier(&u_endp); + if (endp) + *endp = u_endp; + return val * mul; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..29d1f99 --- /dev/null +++ b/util.h @@ -0,0 +1,13 @@ +#ifndef UTIL_H +#define UTIL_H + +#include "types.h" + +#define ROUNDUP(x,n) ((((x) + (n)-1) / (n)) * (n)) + +void hexdump(const byte *buf, uint32_t len); + +unsigned long int strtoul_u(const char *p, char **endp, int base); +unsigned long long int strtoull_u(const char *p, char **endp, int base); + +#endif