aboutsummaryrefslogtreecommitdiffstats
path: root/src/lg-downloader.c
diff options
context:
space:
mode:
authorPaul Kocialkowski <contact@paulk.fr>2016-01-27 20:25:41 +0100
committerPaul Kocialkowski <contact@paulk.fr>2016-01-27 20:28:18 +0100
commit2e31eedce5bc7d2fc37f8cd6be1a7177dd7f6da7 (patch)
treeaf51cb4c77bb03259848bc29352bc2819134e5e1 /src/lg-downloader.c
downloadlg-downloader-2e31eedce5bc7d2fc37f8cd6be1a7177dd7f6da7.tar.gz
lg-downloader-2e31eedce5bc7d2fc37f8cd6be1a7177dd7f6da7.tar.bz2
lg-downloader-2e31eedce5bc7d2fc37f8cd6be1a7177dd7f6da7.zip
lg-downloader: Tool for flashing LG devices' internal storage from download mode
Signed-off-by: Paul Kocialkowski <contact@paulk.fr>
Diffstat (limited to 'src/lg-downloader.c')
-rw-r--r--src/lg-downloader.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/src/lg-downloader.c b/src/lg-downloader.c
new file mode 100644
index 0000000..5c3ecfc
--- /dev/null
+++ b/src/lg-downloader.c
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2016 Paul Kocialkowski <contact@paulk.fr>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "lg-downloader.h"
+#include "gpt.h"
+#include "download.h"
+#include "usb.h"
+
+static int usage_print(void)
+{
+ printf("Usage: lg-downloader [OPTIONS] [OPERATION]\n\n"
+ "Options:\n"
+ " -v verbose\n\n"
+ "Operations:\n"
+ " reboot reboot device\n"
+ " erase [partition] [filename] erase a partition\n"
+ " flash [partition] [filename] flash a file to partition\n"
+ " dump [partition] [filename] dump a partition to file\n");
+}
+
+static int arguments_parse(struct context *context, int argc, char *argv[])
+{
+ int i;
+
+ if (context == NULL || argc < 2)
+ return -1;
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "reboot") == 0) {
+ if (context->operation)
+ return -1;
+
+ context->operation = 'r';
+ } else if (strcmp(argv[i], "erase") == 0) {
+ if (context->operation || argc <= (i + 1))
+ return -1;
+
+ context->operation = 'e';
+ context->partition = argv[++i];
+ context->filename = argv[++i];
+ } else if (strcmp(argv[i], "flash") == 0) {
+ if (context->operation || argc <= (i + 2))
+ return -1;
+
+ context->operation = 'f';
+ context->partition = argv[++i];
+ context->filename = argv[++i];
+ } else if (strcmp(argv[i], "dump") == 0) {
+ if (context->operation || argc <= (i + 2))
+ return -1;
+
+ context->operation = 'd';
+ context->partition = argv[++i];
+ context->filename = argv[++i];
+ } else if (strcmp(argv[i], "partitions") == 0) {
+ if (context->operation)
+ return -1;
+
+ context->operation = 'p';
+ } else if (strcmp(argv[i], "-v") == 0) {
+ context->verbose = 1;
+ } else {
+ return -1;
+ }
+ }
+
+ if (!context->operation)
+ return -1;
+
+ return 0;
+}
+
+int erase(struct context *context)
+{
+ int rc;
+
+ rc = gpt_partition_find(context);
+ if (rc < 0) {
+ fprintf(stderr, "Finding GPT partition failed\n");
+ return -1;
+ }
+
+ if (context->verbose)
+ printf("Erasing partition with address %d blocks and length %d bytes\n", context->address, context->length);
+
+ rc = download_erase(context, context->address, context->length);
+ if (rc < 0)
+ return -1;
+
+ return 0;
+}
+
+int flash(struct context *context)
+{
+ void *buffer = NULL;
+ unsigned int address;
+ unsigned int length;
+ unsigned int count;
+ unsigned int chunk;
+ unsigned char *p;
+ unsigned int i;
+ struct stat st;
+ int fd = -1;
+ int rc;
+
+ if (context == NULL || context->filename == NULL)
+ return -1;
+
+ rc = gpt_partition_find(context);
+ if (rc < 0) {
+ fprintf(stderr, "Finding GPT partition failed\n");
+ goto error;
+ }
+
+ rc = stat(context->filename, &st);
+ if (rc < 0) {
+ fprintf(stderr, "Stating file failed\n");
+ goto error;
+ }
+
+ length = st.st_size;
+
+ if (length > context->length) {
+ fprintf(stderr, "File size (%d bytes) is too big for partition (%d bytes)\n", length, context->length);
+ goto error;
+ }
+
+ fd = open(context->filename, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Opening file failed\n");
+ goto error;
+ }
+
+ if (context->verbose)
+ printf("Writing to partition with address %d blocks and length %d bytes\n", context->address, length);
+
+ buffer = calloc(1, GPT_BLOCK_SIZE);
+
+ rc = download_start_flash_write(context);
+ if (rc < 0)
+ goto error;
+
+ address = context->address;
+ count = 0;
+
+ rc = download_initialize_partition(context, address, GPT_BLOCK_SIZE);
+ if (rc < 0)
+ goto error;
+
+ while (count < length) {
+ chunk = (length - count) < GPT_BLOCK_SIZE ? (length - count) : GPT_BLOCK_SIZE;
+
+ /* Writes are always block-aligned: fill the rest with zeros */
+ if (chunk < GPT_BLOCK_SIZE)
+ memset(buffer, 0, GPT_BLOCK_SIZE);
+
+ p = (unsigned char *) buffer;
+ i = 0;
+
+ while (i < chunk) {
+ rc = read(fd, p, chunk - i);
+ if (rc <= 0)
+ goto error;
+
+ i += rc;
+ p += rc;
+ }
+
+ rc = download_write(context, buffer, address, GPT_BLOCK_SIZE);
+ if (rc < 0)
+ goto error;
+
+ count += chunk;
+ address++;
+ }
+
+ rc = 0;
+ goto complete;
+
+error:
+ rc = -1;
+
+complete:
+ if (buffer != NULL)
+ free(buffer);
+
+ if (fd >= 0)
+ close(fd);
+
+ return rc;
+}
+
+int dump(struct context *context)
+{
+ void *buffer = NULL;
+ unsigned int address;
+ unsigned int length;
+ unsigned int count;
+ unsigned int chunk;
+ unsigned char *p;
+ unsigned int c;
+ int fd = -1;
+ int rc;
+
+ if (context == NULL || context->filename == NULL)
+ return -1;
+
+ rc = gpt_partition_find(context);
+ if (rc < 0) {
+ fprintf(stderr, "Finding GPT partition failed\n");
+ goto error;
+ }
+
+ fd = open(context->filename, O_WRONLY | O_CREAT | O_TRUNC, 644);
+ if (fd < 0) {
+ fprintf(stderr, "Opening file failed\n");
+ goto error;
+ }
+
+ if (context->verbose)
+ printf("Dumping partition with address %d blocks and length %d bytes\n", context->address, context->length);
+
+ buffer = calloc(1, GPT_BLOCK_SIZE);
+
+ address = context->address;
+ length = context->length;
+ count = 0;
+
+ rc = download_ready_read(context, address, GPT_BLOCK_SIZE);
+ if (rc < 0)
+ goto error;
+
+ while (count < length) {
+ chunk = (length - count) < GPT_BLOCK_SIZE ? (length - count) : GPT_BLOCK_SIZE;
+
+ if (chunk != GPT_BLOCK_SIZE) {
+ rc = download_ready_read(context, address, chunk);
+ if (rc < 0)
+ goto error;
+ }
+
+ rc = download_read(context, buffer, chunk);
+ if (rc < 0)
+ goto error;
+
+ c = 0;
+ p = (unsigned char *) buffer;
+
+ while (c < chunk) {
+ rc = write(fd, p, chunk - c);
+ if (rc <= 0)
+ goto error;
+
+ c += rc;
+ p += rc;
+ }
+
+ count += chunk;
+ address++;
+ }
+
+ rc = 0;
+ goto complete;
+
+error:
+ rc = -1;
+
+complete:
+ if (buffer != NULL)
+ free(buffer);
+
+ if (fd >= 0)
+ close(fd);
+
+ return rc;
+}
+
+static int partitions(struct context *context)
+{
+ int rc;
+
+ rc = gpt_partitions_print(context);
+ if (rc < 0)
+ return -1;
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct context context = { 0 };
+ int rc;
+
+ rc = arguments_parse(&context, argc, argv);
+ if (rc < 0) {
+ usage_print();
+ goto error;
+ }
+
+ if (context.verbose)
+ printf("Finding and opening USB device\n");
+
+ rc = usb_open(&context);
+ if (rc < 0)
+ goto error;
+
+ if (context.verbose)
+ printf("Notifying download\n");
+
+ rc = download_notify_start_dl(&context);
+ if (rc < 0)
+ goto error;
+
+ switch (context.operation) {
+ case 'r':
+ printf("Rebooting device\n");
+
+ rc = download_reset(&context);
+ if (rc < 0) {
+ fprintf(stderr, "Rebooting device failed\n");
+ goto error;
+ }
+
+ break;
+ case 'e':
+ printf("Erasing partition %s\n", context.partition);
+
+ /* Give the user a chance to hit Ctrl+C */
+ sleep(1);
+
+ rc = erase(&context);
+ if (rc < 0) {
+ fprintf(stderr, "Erasing partition failed\n");
+ goto error;
+ }
+
+ break;
+ case 'f':
+ printf("Flashing %s to partition %s\n", context.filename, context.partition);
+
+ rc = flash(&context);
+ if (rc < 0) {
+ fprintf(stderr, "Flashing partition failed\n");
+ goto error;
+ }
+
+ break;
+ case 'd':
+ printf("Dumping partition %s to %s\n", context.partition, context.filename);
+
+ rc = dump(&context);
+ if (rc < 0) {
+ fprintf(stderr, "Dumping partition failed\n");
+ goto error;
+ }
+
+ break;
+ case 'p':
+ printf("Reading partitions table\n");
+
+ rc = partitions(&context);
+ if (rc < 0) {
+ fprintf(stderr, "Reading partitions table failed\n");
+ goto error;
+ }
+
+ break;
+ default:
+ goto error;
+ }
+
+ printf("Done!\n");
+
+ rc = 0;
+ goto complete;
+
+error:
+ rc = 1;
+
+complete:
+ usb_close(&context);
+
+ return rc;
+}