--- /dev/null
+/*
+ * tota-ua
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libtar.h>
+#include <zlib.h>
+
+#include "ss_brotli_patch/ss_brotli_patch.h"
+
+void fd_cleanup(int *fd)
+{
+ if (!fd || *fd < 0)
+ return;
+ close(*fd);
+}
+
+void tar_cleanup(TAR **tar)
+{
+ if (!tar || !*tar)
+ return;
+ tar_close(*tar);
+}
+
+enum archive_kind {
+ KIND_RAW,
+ KIND_BROTLI_PATCH,
+};
+
+struct parse_result {
+ enum {
+ PARSE_OK,
+ PARSE_HELP,
+ PARSE_REPEATED_ARGUMENT,
+ PARSE_MISSING_ARGUMENT,
+ PARSE_PATCH_MISTAKE,
+ PARSE_BAD_KIND,
+ PARSE_NO_PARSE,
+ } result;
+
+ const char *archive;
+ const char *dest;
+ const char *archive_file;
+ const char *patch_orig;
+ enum archive_kind kind;
+};
+
+struct parse_result parse_args(int argc, char **argv)
+{
+ const char *archive = NULL;
+ const char *dest = NULL;
+ const char *archive_file = NULL;
+ const char *patch_orig = NULL;
+ enum archive_kind kind = -1;
+ bool help = false;
+
+ for (;;) {
+ const struct option long_options[] = {
+ {"archive", required_argument, NULL, 0 },
+ {"dest", required_argument, NULL, 1 },
+ {"archive-file", required_argument, NULL, 2 },
+ {"kind", required_argument, NULL, 3 },
+ {"patch-orig", required_argument, NULL, 4 },
+ {"help", no_argument, NULL, 'h'},
+ {0}
+ };
+ int option = getopt_long(argc, argv, "h", long_options, NULL);
+ if (option < 0)
+ break;
+
+ switch (option) {
+ case 0: // archive
+ if (archive != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ archive = optarg;
+ break;
+
+ case 1: // dest
+ if (dest != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ dest = optarg;
+ break;
+
+ case 2: // archive-file
+ if (archive_file != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ archive_file = optarg;
+ break;
+
+ case 3: // kind
+ if (kind != -1)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ if (strcmp(optarg, "raw") == 0)
+ kind = KIND_RAW;
+ else if (strcmp(optarg, "ss_brotli_patch") == 0)
+ kind = KIND_BROTLI_PATCH;
+ else
+ return (struct parse_result) { .result = PARSE_BAD_KIND };
+ break;
+
+ case 4: // patch-orig
+ if (patch_orig != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ patch_orig = optarg;
+ break;
+
+ case 'h': // help
+ if (help)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ help = true;
+ break;
+
+ default:
+ return (struct parse_result) { .result = PARSE_NO_PARSE };
+ }
+ }
+
+ if (help) {
+ if (archive != NULL || dest != NULL || archive_file != NULL || patch_orig != NULL || kind != -1)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ return (struct parse_result) { .result = PARSE_HELP };
+ }
+
+ if (archive == NULL)
+ return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT };
+ if (dest == NULL)
+ return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT };
+ if (archive_file == NULL)
+ return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT };
+ if (kind == -1)
+ return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT };
+
+ if (patch_orig == NULL && kind == KIND_BROTLI_PATCH)
+ return (struct parse_result) { .result = PARSE_PATCH_MISTAKE };
+ if (patch_orig != NULL && kind == KIND_RAW)
+ return (struct parse_result) { .result = PARSE_PATCH_MISTAKE };
+
+ return (struct parse_result) {
+ .result = PARSE_OK,
+ .archive = archive,
+ .dest = dest,
+ .archive_file = archive_file,
+ .patch_orig = patch_orig,
+ .kind = kind,
+ };
+}
+
+// The TAR structure can only hold an int as a FD.
+// libtar example uses some brutal hack which we don't want to do.
+// We don't want to open multiple archive at the time, so let's just have 1 as FD,
+// and instead hold the gzip structure globally.
+static gzFile gzip_file;
+
+int gzip_open(const char *pathname, int oflags, ...)
+{
+ if (gzip_file != NULL) {
+ errno = EALREADY;
+ return -1;
+ }
+
+ // We assume oflags == O_RDONLY, since that's what we actually use.
+ // Also we don't care about the mode since we are lazy.
+ if (oflags != O_RDONLY) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ __attribute__((cleanup(fd_cleanup))) int inner_fd = open(pathname, O_RDONLY);
+ if (inner_fd == -1)
+ return -1;
+
+ gzip_file = gzopen(pathname, "r");
+ if (gzip_file == NULL)
+ return -1;
+ inner_fd = -1;
+
+ return 1;
+}
+
+int gzip_close(__attribute__((unused)) int useless_fd)
+{
+ if (gzip_file == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return gzclose(gzip_file);
+}
+
+ssize_t gzip_read(__attribute__((unused)) int useless_fd, void *buf, size_t len)
+{
+ if (gzip_file == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return gzread(gzip_file, buf, len);
+}
+
+ssize_t gzip_write(__attribute__((unused)) int useless_fd, __attribute__((unused)) const void *buf, __attribute__((unused)) size_t len)
+{
+ // Again, we do not use this.
+ errno = ENOTSUP;
+ return -1;
+}
+
+void help(char *name)
+{
+ fprintf(stderr,
+ "Usage: %s --archive ARCHIVE --archive-file FILE --dest DESTINATION --kind KIND [--patch-orig ORIGIN]\n"
+ "\n"
+ "Patches a partition using an archive.\n"
+ "It will look for a file named FILE in an archive called ARCHIVE; the archive should have an extension\n"
+ "of either .tar, .tar.gz or .tgz; it will be read in the way corresponding to the extension.\n"
+ "The KIND parameter specifies how the file will be treated:\n"
+ " - raw means that the file should be treated as an image to be written,\n"
+ " - ss_brotli_patch means that the file should be treated as a delta in the (Tizen-specific) ss_brotli_patch\n"
+ " format. In this case, ORIGIN is needed and specifies the partition that a delta is based on.\n"
+ "The results will be written to the DESTINATION partition.\n",
+ name);
+}
+
+int apply_raw(const char *dest, TAR *tar)
+{
+ int size = th_get_size(tar);
+
+ __attribute__((cleanup(fd_cleanup))) int out = open(dest, O_WRONLY);
+ if (out == -1) {
+ fprintf(stderr, "Couldn't open the target (errno: %m)\n");
+ return -1;
+ }
+
+ while (size > 0) {
+ char buf[T_BLOCKSIZE];
+ int r_read = tar_block_read(tar, buf);
+ switch (r_read) {
+ case -1:
+ fprintf(stderr, "Couldn't read from the archive (errno: %m)\n");
+ return -1;
+ case 0:
+ fprintf(stderr, "We have reached EOF unexpectedly\n");
+ return -1;
+ }
+
+ if (r_read > size) {
+ r_read = size;
+ }
+ size -= r_read;
+
+ int written = 0;
+ while (written - r_read) {
+ int r_write = write(out, buf + written, r_read - written);
+ if (r_write == -1) {
+ fprintf(stderr, "Couldn't write to the partition (errno: %m)\n");
+ return -1;
+ }
+ written += r_write;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct parse_result parsed = parse_args(argc, argv);
+ switch (parsed.result) {
+ case PARSE_REPEATED_ARGUMENT:
+ fprintf(stderr, "Argument incorrectly repeated\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_MISSING_ARGUMENT:
+ fprintf(stderr, "Argument incorrectly missing\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_PATCH_MISTAKE:
+ fprintf(stderr, "`patch-orig` parameter doesn't fit the kind\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_BAD_KIND:
+ fprintf(stderr, "Invalid `kind` parameter (possible: `raw`, `ss_brotli_patch`)\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_NO_PARSE:
+ fprintf(stderr, "Invalid parameters passed\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_HELP:
+ help(argv[0]);
+ return EXIT_SUCCESS;
+ case PARSE_OK:
+ break;
+ }
+
+ tartype_t *type;
+ static tartype_t gzip_type = { gzip_open, gzip_close, gzip_read, gzip_write };
+ if (strcmp(parsed.archive + strlen(parsed.archive) - strlen(".tar"), ".tar") == 0)
+ type = NULL;
+ else if (strcmp(parsed.archive + strlen(parsed.archive) - strlen(".tar.gz"), ".tar.gz") == 0)
+ type = &gzip_type;
+ else if (strcmp(parsed.archive + strlen(parsed.archive) - strlen(".tgz"), ".tgz") == 0)
+ type = &gzip_type;
+ else {
+ fprintf(stderr, "Unknown archive extension\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ __attribute__ ((cleanup(tar_cleanup))) TAR *tar;
+ if (tar_open(&tar, parsed.archive, type, O_RDONLY, 0, 0) == -1) {
+ /* Usually we would instead initialize tar to NULL, and tar_open would not touch it in case of failure.
+ * Alas, that's not what happens; tar_open sets tar to a newly allocated address, does stuff there,
+ * and in case of failure frees the address but does not reset it. This means that in case of failure
+ * tar contains an invalid address. So we have to NULL it out (so tar_cleanup doesn't crash our program),
+ * and then we don't have to initialize tar to NULL in first place. */
+ tar = NULL;
+
+ fprintf(stderr, "Couldn't open the archive (errno: %m)\n");
+ return EXIT_FAILURE;
+ }
+
+ for (;;) {
+ switch (th_read(tar)) {
+ case -1:
+ fprintf(stderr, "Couldn't read the archive header (errno: %m)\n");
+ return EXIT_FAILURE;
+ case 1:
+ // All files processed, which means...
+ fprintf(stderr, "File not found in archive\n");
+ return EXIT_FAILURE;
+ case 0:
+ break;
+ }
+
+ const char *this_name = th_get_pathname(tar);
+ if (this_name == NULL) {
+ // TODO: Can this even happen? I don't think docs say anything about this.
+ fprintf(stderr, "Couldn't extract the filename from the archive (malformed archive?)\n");
+ return EXIT_FAILURE;
+ }
+
+ if (strcmp(this_name, parsed.archive_file) != 0) {
+ if (TH_ISREG(tar)) {
+ if (tar_skip_regfile(tar) == -1) {
+ fprintf(stderr, "Couldn't skip an intermediate file from the archive (errno: %m; malformed archive?)\n");
+ return EXIT_FAILURE;
+ }
+ }
+ continue;
+ }
+
+ fprintf(stderr, "File found. Starting to write\n");
+
+ int r;
+ switch (parsed.kind) {
+ case KIND_RAW:
+ r = apply_raw(parsed.dest, tar);
+ break;
+
+ case KIND_BROTLI_PATCH:
+ r = apply_patch_brotli(parsed.patch_orig, parsed.dest, tar);
+ break;
+ }
+
+ if (r != 0) {
+ fprintf(stderr, "Couldn't apply; you might need to restore from the backup!\n");
+ return EXIT_FAILURE;
+ }
+
+ fprintf(stderr, "Everything written successfully\n");
+ return EXIT_SUCCESS;
+ }
+}
/*
- * libtota
+ * tota-ua (previously libtota)
*
* Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
-#include "fota_log.h"
+#include <libtar.h>
#define PF_OK 0
#define PF_ERROR_OPEN_FILE 1
#define PF_ERROR_INVALID_PATCH_FILE 3
#define PF_ERROR_DECOMPRESSION 4
-#define BUFF_IN_LEN 4096
#define BUFF_OUT_LEN 4096
#define SSINT_LEN 8
#define BLOCK_COUNT_REPORT 5000
const char SSDIFF_MAGIC[] = "SSDIFF40";
struct bs_data {
- int src_fd, dest_fd, patch_fd;
- void *src_ptr, *dest_ptr, *patch_ptr;
+ int src_fd, dest_fd;
+ void *src_ptr, *dest_ptr;
+ TAR *patch_tar;
size_t src_len, dest_len, patch_len;
- unsigned char buff_in[BUFF_IN_LEN];
- unsigned char buff_out[BUFF_IN_LEN];
+ unsigned char buff_in[T_BLOCKSIZE];
+ unsigned char buff_out[BUFF_OUT_LEN];
uint8_t *dest_pos;
uint8_t *src_pos;
+ size_t patch_remaining;
size_t available_in, available_out;
const uint8_t *compressed_pos;
uint8_t *decompressed_pos;
if (data->src_ptr) munmap(data->src_ptr, data->src_len);
if (data->dest_ptr) munmap(data->dest_ptr, data->dest_len);
- if (data->patch_ptr) munmap(data->patch_ptr, data->patch_len);
if (data->src_fd) close(data->src_fd);
- if (data->patch_fd) close(data->patch_fd);
if (data->dest_fd) close(data->dest_fd);
}
-static int open_file(char *file_name, int mode)
+static int open_file(const char *file_name, int mode)
{
assert(file_name);
int fd = open(file_name, mode, S_IWUSR | S_IRUSR);
if (fd < 0)
- LOGE("Open file %s error: %m (%d)\n", file_name, errno);
+ fprintf(stderr, "Open file %s error: %m (%d)\n", file_name, errno);
return fd;
}
data->decompressed_pos = data->buff_out + keep_offset;
data->available_out = sizeof(data->buff_out) - keep_offset;
- BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
-
- result = BrotliDecoderDecompressStream(data->bstate,
- &data->available_in,
- &data->compressed_pos,
- &data->available_out,
- &data->decompressed_pos,
- &data->total_size);
-
- if (result == BROTLI_DECODER_RESULT_ERROR) {
- LOGE("Decoder error\n");
- return PF_ERROR_DECOMPRESSION;
- } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
- LOGE("Invalid source file\n");
- return PF_ERROR_DECOMPRESSION;
+ BrotliDecoderResult result;
+
+ for (;;) {
+ result =
+ BrotliDecoderDecompressStream(data->bstate,
+ &data->available_in,
+ &data->compressed_pos,
+ &data->available_out,
+ &data->decompressed_pos,
+ &data->total_size);
+
+ if (result == BROTLI_DECODER_RESULT_ERROR) {
+ fprintf(stderr, "Brotli decompression errored\n");
+ return PF_ERROR_DECOMPRESSION;
+ } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
+ if (data->patch_remaining <= 0) {
+ fprintf(stderr, "We have ran out of data and decompression is still in progress\n");
+ return PF_ERROR_DECOMPRESSION;
+ }
+
+ int r_read = tar_block_read(data->patch_tar, data->buff_in);
+ switch (r_read) {
+ case -1:
+ fprintf(stderr, "Couldn't read from the archive (errno: %m)\n");
+ return PF_ERROR_DECOMPRESSION;
+ case 0:
+ fprintf(stderr, "We have reached EOF unexpectedly\n");
+ return PF_ERROR_DECOMPRESSION;
+ }
+
+ if (r_read > data->patch_remaining) {
+ r_read = data->patch_remaining;
+ }
+ data->patch_remaining -= r_read;
+
+ data->available_in = r_read;
+ data->compressed_pos = data->buff_in;
+ } else {
+ break;
+ }
}
return PF_OK;
}
-static int open_files(struct bs_data *data, char *source_file, size_t source_size, char *dest_file, size_t dest_size, char *patch_file)
+static int open_files(struct bs_data *data, const char *source_file, const char *dest_file, TAR *patch_tar)
{
assert(data);
assert(source_file);
assert(dest_file);
- assert(patch_file);
+ assert(patch_tar);
data->src_fd = open_file(source_file, O_RDONLY);
- data->patch_fd = open_file(patch_file, O_RDONLY);
data->dest_fd = open_file(dest_file, O_RDWR);
if (data->src_fd < 0 ||
- data->patch_fd < 0 ||
data->dest_fd < 0)
return PF_ERROR_OPEN_FILE;
- data->src_len = source_size;
- data->patch_len = get_file_len(data->patch_fd);
- data->dest_len = dest_size;
+ data->patch_tar = patch_tar;
+
+ data->src_len = get_file_len(data->src_fd);
+ data->patch_len = th_get_size(data->patch_tar);
+ data->dest_len = get_file_len(data->dest_fd);
data->src_ptr = mmap(NULL, data->src_len, PROT_READ, MAP_PRIVATE, data->src_fd, 0);
if (data->src_ptr == MAP_FAILED) {
- LOGE("mmap source file error: %m (%d)", errno);
- return PF_ERROR_MMAP;
- }
-
- data->patch_ptr = mmap(NULL, data->patch_len, PROT_READ, MAP_PRIVATE, data->patch_fd, 0);
- if (data->patch_ptr == MAP_FAILED) {
- LOGE("mmap patch file error: %m (%d)", errno);
+ fprintf(stderr, "mmap source file error: %m (%d)\n", errno);
return PF_ERROR_MMAP;
}
data->dest_ptr = mmap(NULL, data->dest_len, PROT_WRITE, MAP_SHARED, data->dest_fd, 0);
if (data->dest_ptr == MAP_FAILED) {
- LOGE("mmap destination error: %m (%d)\n", errno);
+ fprintf(stderr, "mmap destination error: %m (%d)\n", errno);
return PF_ERROR_MMAP;
}
- data->compressed_pos = data->patch_ptr;
- data->available_in = data->patch_len;
+ data->patch_remaining = data->patch_len;
return PF_OK;
}
assert(data);
data->src_fd = -1;
- data->patch_fd = -1;
data->dest_fd = -1;
data->src_ptr = NULL;
data->dest_ptr = NULL;
- data->patch_ptr = NULL;
+ data->patch_tar = NULL;
data->src_len = 0;
data->dest_len = 0;
data->patch_len = 0;
+ data->patch_remaining = 0;
data->available_in = 0;
data->compressed_pos = 0;
data->available_out = 0;
if (*buff_out_pos + sizeof(SSDIFF_MAGIC) > data->decompressed_pos ||
memcmp(data->buff_out, SSDIFF_MAGIC, sizeof(SSDIFF_MAGIC) - 1) != 0) {
- LOGE("Invalid patch file\n");
+ fprintf(stderr, "Invalid patch file\n");
return PF_ERROR_INVALID_PATCH_FILE;
} else {
- LOGL(LOG_SSENGINE, "Looks like SSDIFF\n");
+ fprintf(stderr, "Looks like SSDIFF\n");
}
*buff_out_pos += sizeof(SSDIFF_MAGIC) - 1;
}
size_t target_size = parse_ssint(*buff_out_pos);
- LOGL(LOG_SSENGINE, "target_size: 0x%lx (%ld)\n", target_size, target_size);
+ fprintf(stderr, "target_size: 0x%lx (%ld)\n", target_size, target_size);
if (target_size != data->dest_len) {
- LOGE("Declared target size differs from that read from the patch\n");
+ fprintf(stderr, "Declared target size differs from that read from the patch\n");
return PF_ERROR_INVALID_PATCH_FILE;
}
return PF_OK;
}
-int apply_patch_brotli(char *source_file, size_t source_size, char *dest_file, size_t dest_size, char *patch_file)
+int apply_patch_brotli(const char *source_file, const char *dest_file, TAR *patch_tar)
{
assert(source_file);
assert(dest_file);
- assert(patch_file);
+ assert(patch_tar);
int result;
uint64_t blocks = 0;
init_data(&data);
- if ((result = open_files(&data, source_file, source_size, dest_file, dest_size, patch_file)) != PF_OK)
+ if ((result = open_files(&data, source_file, dest_file, patch_tar)) != PF_OK)
goto exit;
if ((result = decompress_bytes(&data, 0)) != PF_OK)
blocks++;
if (blocks % BLOCK_COUNT_REPORT == 0) {
- printf("Number of processed patch blocks: %lld\n", blocks);
+ fprintf(stderr, "Number of processed patch blocks: %lu\n", blocks);
}
}
result = PF_OK;
exit:
- printf("Total processed blocks: %lld\n", blocks);
+ fprintf(stderr, "Total processed blocks: %lu\n", blocks);
free_data(&data);
return result;
}