ADD_SUBDIRECTORY(data)
ADD_SUBDIRECTORY(scripts/rw-upgrade)
ADD_SUBDIRECTORY(src/dynamic-partitions)
+ADD_SUBDIRECTORY(src/copy-blockdev)
\ No newline at end of file
/bin/blkid-print
/usr/sbin/parse-dynparts
/bin/resize-dynparts
+/bin/copy-blockdev
"
DIRECTORIES="
Requires: upgrade-engine = %{version}-%{release}
Requires: parse-dynparts = %{version}-%{release}
Requires: resize-dynparts = %{version}-%{release}
+Requires: copy-blockdev = %{version}-%{release}
%description
Metapackage requiring all upgrade-related packages on the platform.
This package provides utility needed to modify metadata of a super partition by
changing size of dynamic partitions.
+%package -n copy-blockdev
+Summary: Utility which copies block devices and updates upgrade status
+
+%description -n copy-blockdev
+This package provides utility which copies block device to another and updates
+upgrade status.
+
%prep
%setup -q
%manifest upgrade.manifest
%license LICENSE.Apache-2.0
%{_bindir}/resize-dynparts
+
+%files -n copy-blockdev
+%manifest upgrade.manifest
+%license LICENSE.MIT
+%{_bindir}/copy-blockdev
--- /dev/null
+IF(NOT DEFINE_HOST_BUILD)
+SET(CMAKE_C_FLAGS ENV${CFLAGS})
+INCLUDE(FindPkgConfig)
+pkg_check_modules(REQUIRED_PKGS REQUIRED hal-api-device)
+FOREACH(flag ${REQUIRED_PKGS_CFLAGS})
+ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+SET(CMAKE_C_FLAGS ${EXTRA_CFLAGS})
+ENDIF()
+
+set(COPY_BLOCKDEVSRCS ../upgrade-apply/sha1/sha1.c main.c lib.c)
+include_directories(../upgrade-apply/sha1)
+set(COPY_BLOCKDEVEXENAME "copy-blockdev")
+
+add_executable(${COPY_BLOCKDEVEXENAME} ${COPY_BLOCKDEVSRCS})
+target_compile_options(${COPY_BLOCKDEVEXENAME} PRIVATE -Wall -Wextra -pedantic -fPIE)
+target_compile_definitions(${COPY_BLOCKDEVEXENAME} PRIVATE -D_FILE_OFFSET_BITS=64)
+
+IF(NOT DEFINE_HOST_BUILD)
+target_link_libraries(${COPY_BLOCKDEVEXENAME} ${REQUIRED_PKGS_LDFLAGS})
+ELSE()
+target_compile_definitions(${COPY_BLOCKDEVEXENAME} PRIVATE -DHOST_BUILD)
+ENDIF()
+
+install(TARGETS ${COPY_BLOCKDEVEXENAME} DESTINATION ${BINDIR})
--- /dev/null
+# Benchmarks of copy-blockdev
+
+## Methodology
+
+The tests were performed on RPI 4B and GOODRAM 32 GB SD card (UHS Speed Class 1, Video Speed Class V10). Custom build of Tizen with dynamic partitions and `copy-blockdev` added to `upgrade` package was used:
+
+```
+NAME=Tizen
+VERSION="9.0.0 (Tizen9.0/Unified)"
+ID=tizen
+VERSION_ID=9.0.0
+PRETTY_NAME="Tizen 9.0.0 (Tizen9.0/Unified)"
+ANSI_COLOR="0;36"
+CPE_NAME="cpe:/o:tizen:tizen:9.0.0"
+BUILD_ID=tizen-unified_20240415.043042_tizen-headless-aarch64-202404161625
+```
+
+The tests consisted of copying `rootfs_a` to `rootfs_b` and measuring execution time with `time` tool. In case of Tizen with dynamic partitions, both partitions were mapped on a `super` partition and visible as `/dev/mapper/rootfs_a` and `/dev/mapper/rootfs_b` block devices having 662786048 B (~632 MB).
+
+Two tests were performed:
+
+1. `copy-blockdev` with `partsize` set to 4096 (`copy-blockdev /dev/mapper/rootfs_a /dev/mapper/rootfs_b --partsize 4096`) was compared to `head` (`head-c 662786048 /dev/mapper/rootfs_a > /dev/mapper/rootfs_b`) and `dd` (`dd if=/dev/mapper/rootfs_a of=/dev/mapper/rootfs_b bs=4096`). Data were gathered using `benchmark_comparison.sh` and processed using `process_comparison.py`.
+2. `copy-blockdev` was executed with different values of partsize, starting from 2^9 (512 B) to 2^30 (1073741824 B). Additionaly, maximum partsize equal to 2147479552 B was tested. Data were gathered using `banchmark-partsize.sh` and processed using `process_partsize.py`.
+
+Each command was executed ten times. Mean and std of real, user and sys times reported by `time` command were calculated.
+
+## Results
+
+### Comparison of copy-blockdevice with head and dd
+
+| | mean (real) [s] | std (real) [s]| mean (user) [s] | std (user) [s]| mean (sys) [s] | std (sys) [s]|
+|--------------|-----------------|---------------|-----------------|---------------|----------------|--------------|
+|copy-blockdev | 53.134 | 1.007 | 0.230 | 0.024 | 6.712 | 0.072 |
+|head | 54.617 | 0.933 | 0.401 | 0.044 | 6.391 | 0.126 |
+|dd | 55.647 | 2.843 | 0.429 | 0.032 | 6.572 | 0.108 |
+
+All tools have similar performance. `copy-blockdev` is the fastest, but only by a little. It has shortest mean user time, on the other hand it had largest average sys time. Nevertheless, these times are so small in comparison to real time that they have no effect on the overall result.
+
+Throughput of each tool was ~12.5MB/s. This means that probably copying speed was limited by SD card capabilities used in tests.
+
+### copy-blockdev executed with different values of partsize
+
+| partsize [B] | mean(real) [s] | std(real) [s] | mean(user) [s] | std(user) [s] | mean(sys) [s] | std(sys) [s] |
+|---------------|----------------|---------------|----------------|---------------|---------------|-------------|
+| 512 | 123.930 | 1.901 | 1.820 | 0.074 | 23.405 | 0.233 |
+| 1024 | 118.682 | 2.037 | 0.939 | 0.052 | 19.048 | 0.180 |
+| 2048 | 114.639 | 0.550 | 0.489 | 0.035 | 16.509 | 0.092 |
+| 4096 | 54.142 | 2.171 | 0.218 | 0.031 | 6.764 | 0.080 |
+| 8192 | 53.861 | 2.250 | 0.137 | 0.021 | 6.132 | 0.077 |
+| 16384 | 53.447 | 1.419 | 0.057 | 0.016 | 5.843 | 0.070 |
+| 32768 | 53.531 | 1.699 | 0.039 | 0.016 | 5.646 | 0.068 |
+| 65536 | 53.848 | 1.401 | 0.017 | 0.010 | 5.667 | 0.062 |
+| 131072 | 53.124 | 1.592 | 0.014 | 0.006 | 5.619 | 0.062 |
+| 262144 | 53.502 | 1.922 | 0.010 | 0.007 | 5.635 | 0.086 |
+| 524288 | 53.163 | 1.021 | 0.005 | 0.006 | 5.597 | 0.055 |
+| 1048576 | 53.813 | 1.897 | 0.003 | 0.003 | 5.597 | 0.058 |
+| 2097152 | 54.440 | 2.381 | 0.004 | 0.002 | 5.581 | 0.084 |
+| 4194304 | 52.687 | 0.768 | 0.002 | 0.002 | 5.558 | 0.048 |
+| 8388608 | 54.112 | 1.917 | 0.004 | 0.002 | 5.560 | 0.067 |
+| 16777216 | 52.852 | 0.869 | 0.002 | 0.001 | 5.581 | 0.075 |
+| 33554432 | 52.595 | 0.834 | 0.001 | 0.002 | 5.600 | 0.074 |
+| 67108864 | 53.617 | 1.416 | 0.002 | 0.002 | 5.586 | 0.073 |
+| 134217728 | 53.992 | 2.273 | 0.002 | 0.002 | 5.565 | 0.076 |
+| 268435456 | 54.449 | 2.422 | 0.002 | 0.002 | 5.584 | 0.050 |
+| 536870912 | 53.417 | 1.892 | 0.001 | 0.001 | 5.516 | 0.072 |
+| 1073741824 | 53.602 | 1.974 | 0.002 | 0.002 | 5.588 | 0.077 |
+| 2147479552 | 52.739 | 0.767 | 0.002 | 0.002 | 5.574 | 0.062 |
+
+If `partsize` is less than size of a block (4096 B), performance of copying decreases. Increasing `partsize` above 4096 B has almost no effect on copying speed (again, it seems that it was limited by SD card capabilities), but it is visible that the larger the `partsize`, the shorter `user` time since `copy-blockdev` spends less time invoking `sendfile` in a loop.
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+
+if [ "$#" -ne 3 ]; then
+ echo "Usage: ./$(basename "$0") dev_src dev_dst result_dir"
+ exit 1
+fi
+
+src=$1
+dst=$2
+result_dir=$3
+
+read -p "This script will copy $src to $dst. $dst will be overwritten! Do you wish to continue (y/n)?: " answer
+
+if [ "$answer" != "y" ]; then
+ exit 0
+fi
+
+cnt_src="$(blockdev --getsize64 "$src")"
+cnt_dst="$(blockdev --getsize64 "$dst")"
+cnt=0
+
+if [ "$cnt_src" -lt "$cnt_dst" ]; then
+ cnt=$cnt_src
+else
+ cnt=$cnt_dst
+fi
+
+for i in {1..10}; do
+ echo 3 > /proc/sys/vm/drop_caches
+ (time copy-blockdev "$src" "$dst" --partsize 4096) 2>> "$result_dir"/result_copy-blockdev.txt
+ echo 3 > /proc/sys/vm/drop_caches
+ (time head -c "$cnt" "$src" > "$dst") 2>> "$result_dir"/result_head.txt
+ echo 3 > /proc/sys/vm/drop_caches
+ (time dd if="$src" of="$dst" bs=4096) 2>> "$result_dir"/result_dd.txt
+done
--- /dev/null
+#!/bin/bash
+
+if [ "$#" -ne 3 ]; then
+ echo "Usage: ./$(basename "$0") dev_src dev_dst result_dir"
+ exit 1
+fi
+
+devin=$1
+devout=$2
+result_dir=$3
+
+read -p "This script will copy $devin to $devout. $devout will be overwritten! Do you wish to continue (y/n)?: " answer
+
+if [ "$answer" != "y" ]; then
+ exit 0
+fi
+
+# perform test for partsize in <512,1073741824> [B].
+for i in {9..30}; do
+ cnt=$((2 ** i))
+ echo "Benchmarking partsize = $cnt"
+
+ for j in {0..9}; do
+ echo 3 > /proc/sys/vm/drop_caches
+ (time copy-blockdev "$devin" "$devout" --partsize $cnt) 2>> "$result_dir"/result_$cnt.txt
+ done
+done
+
+cnt=2147479552 # max count for sendfile (less than 2^31)
+echo "Benchmarking partsize = $cnt"
+
+for j in {0..9}; do
+ echo 3 > /proc/sys/vm/drop_caches
+ (time copy-blockdev "$devin" "$devout" --partsize $cnt) 2>> "$result_dir"/result_$cnt.txt
+done
--- /dev/null
+#!/usr/bin/env python3
+
+import re
+import math
+
+def process(fname):
+ file = open(fname)
+ lines = file.readlines()
+ file.close()
+
+ results = []
+ i = 0
+ result = []
+ for line in lines:
+ if i > 2:
+ i = 0
+ results.append(result.copy())
+ result.clear()
+ else:
+ numbers=re.findall(r"[-+]?(?:\d+(?:\.\d+)?)", line)
+ result.append(float(numbers[0]) * 60 + float(numbers[1]))
+ i +=1
+
+ results.append(result.copy())
+ return results
+
+def mean(results):
+ real_mean = 0
+ user_mean = 0
+ sys_mean = 0
+
+ for el in results:
+ real_mean += el[0]
+ user_mean += el[1]
+ sys_mean += el[2]
+
+ real_mean /= len(results)
+ user_mean /= len(results)
+ sys_mean /= len(results)
+
+ return [round(real_mean, 3), round(user_mean, 3), round(sys_mean, 3)]
+
+def std(results):
+ mean_val = mean(results)
+ real_std = 0
+ user_std = 0
+ sys_std = 0
+
+ for el in results:
+ real_std += (el[0] - mean_val[0]) * (el[0] - mean_val[0])
+ user_std += (el[1] - mean_val[1]) * (el[1] - mean_val[1])
+ sys_std += (el[2] - mean_val[2]) * (el[2] - mean_val[2])
+
+ real_std = math.sqrt(real_std/(len(results) - 1))
+ user_std = math.sqrt(user_std/(len(results) - 1))
+ sys_std = math.sqrt(sys_std/(len(results) - 1))
+
+ return [round(real_std, 3), round(user_std, 3), round(sys_std, 3)]
--- /dev/null
+#!/usr/bin/env python3
+
+import re
+import functions
+
+cbd_result = functions.process("comparison/results/result_copy-blockdev.txt")
+cbd_mean = functions.mean(cbd_result)
+cbd_std = functions.std(cbd_result)
+
+head_result = functions.process("comparison/results/result_head.txt")
+head_mean = functions.mean(head_result)
+head_std = functions.std(head_result)
+
+dd_result = functions.process("comparison/results/result_dd.txt")
+dd_mean = functions.mean(dd_result)
+dd_std = functions.std(dd_result)
+
+print("\nMean & std time:\n")
+print("| | mean (real) [s] | std (real) [s]| mean (user) [s] | std (user) [s]| mean (sys) [s] | std (sys) [s]|")
+print("|--------------|-----------------|---------------|-----------------|---------------|----------------|--------------|")
+print("|copy-blockdev | ", cbd_mean[0], " | ", cbd_std[0], " |", cbd_mean[1], " | ", cbd_std[1], " |", cbd_mean[2], " | ", cbd_std[2], " |");
+print("|head | ", head_mean[0], " | ", head_std[0], " |", head_mean[1], " | ", head_std[1], " |", head_mean[2], " | ", head_std[2], " |");
+print("|dd | ", dd_mean[0], " | ", dd_std[0], " |", dd_mean[1], " | ", dd_std[1], " |", dd_mean[2], " | ", dd_std[2], " |");
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python3
+
+import re
+import functions
+import math
+
+results=[]
+header = "| partsize [B] | mean(real) [s] | std(real) [s] | mean(user) [s] | std(user) [s] | mean(sys) [s] | std(sys) [s] |"
+div = "|---------------|----------------|---------------|----------------|---------------|---------------|--------------|"
+rows = ""
+
+for i in range(9, 31):
+ suffix = 2 ** i
+ result = functions.process("partsize/results/result_" + str(suffix) + ".txt")
+ result_mean = functions.mean(result)
+ result_std = functions.std(result)
+ #print("partsize = ", suffix, "\n")
+ #print(result, "\n")
+ #print(result_mean, "\n")
+
+ rows += "| "
+ rows += str(suffix) + " | "
+
+ rows += str(result_mean[0]) + " | "
+ rows += str(result_std[0]) + " | "
+ rows += str(result_mean[1]) + " | "
+ rows += str(result_std[1]) + " | "
+ rows += str(result_mean[2]) + " | "
+ rows += str(result_std[2]) + " |\n"
+
+
+result = functions.process("partsize/results/result_2147479552.txt")
+result_mean = functions.mean(result)
+result_std = functions.std(result)
+rows += "| 2147479552 | "
+rows += str(result_mean[0]) + " | "
+rows += str(result_std[0]) + " | "
+rows += str(result_mean[1]) + " | "
+rows += str(result_std[1]) + " | "
+rows += str(result_mean[2]) + " | "
+rows += str(result_std[2]) + " |\n"
+
+print(header)
+print(div)
+print(rows)
\ No newline at end of file
--- /dev/null
+/* Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: Apache-2.0 */
+
+#include "lib.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <upgrade-apply/sha1/sha1.h>
+
+#ifndef HOST_BUILD
+#include <hal/device/hal-board.h>
+#endif
+
+void usage(char *name) {
+ fprintf(stderr, "Usage:\n"
+ "\n"
+ " %s src dst [options]\n"
+ "or"
+ " %s --help\n"
+ "\n"
+ "Copy src block device to dst block device and show progress.\n"
+ "\n"
+ "Options:\n"
+ " -c, --cnt <num>\t\tcopy <num> bytes. <num> >= 0\n"
+ " -f, --progress-from <num>\tstart progress from <num> [%%]. <num> >= 0\n"
+ " -t, --progress-to <num>\tstop progress on <num> [%%]. <num> <= 100 && <num> >= --progress-from value\n"
+ " -h, --help\t\t\tshow help\n"
+ " -u, --update\t\t\tUpdate progress of copying. Print it to stdout\n"
+ " -p, --partsize <num>\bytes transferred by each sendfile = <num>. 0 < <num> <= 2147479552\n"
+ " -s, --sha1 calculate and check SHA1 checksums after copying\n"
+ "\n"
+ "Notes\n"
+ " If -c is not set then size of src and dst will be checked and min(size(src), size(dst)) will be copied.\n"
+ " If -c is set but it exceeds size of min(size(src), size(dst), error will be returned.\n"
+ " If -f and/or -t are set then progress of copying will be normalized to these values. If not set, they default to -f 0 and -t 100.\n"
+ " If -u is not set, -f and -t are used but status is not updated.\n"
+ " If -p is not set, 1MiB (1048576) will be used by default.\n"
+ "\n"
+ "Return values:\n"
+ " %d\tsuccess\n"
+ " %d\twrong number of arguments\n"
+ " %d\tsrc and dst are the same file\n"
+ " %d\twrong value of cnt\n"
+ " %d\twrong value of progress-from\n"
+ " %d\twrong value of progress-to\n"
+ " %d\twrong value of partsize\n"
+ " %d\tunknown option\n"
+ " %d\tcan't open src to read\n"
+ " %d\tcan't open dst to write\n"
+ " %d\terror during copying\n"
+ " %d\tcan't check file size or src/dst is not a block device\n"
+ " %d\tError during SHA1 checksum calculation of src\n"
+ " %d\tError during SHA1 checksum calculation of dst\n"
+ " %d\tSHA1 checksum of src and dst after copying do not match\n"
+ " %d\tSHA1 Cannot set upgrade status\n",
+ name, name, EXIT_SUCCESS, ERR_WRONG_ARGC, ERR_SAME_FILE,
+ ERR_WRONG_CNT, ERR_WRONG_START, ERR_WRONG_STOP, ERR_WRONG_PARTSIZE,
+ ERR_WRONG_ARG, ERR_SRC_OPEN, ERR_DST_OPEN, ERR_SENDFILE, ERR_CHECK_SIZE,
+ ERR_SHA1_SRC, ERR_SHA1_DST, ERR_SHA1_COMP, ERR_STATUS);
+
+ return;
+}
+
+off_t get_blockdev_size(char *filename) {
+ if (filename == NULL) return -1;
+
+ off_t src_size = 0;
+ struct stat stat_src;
+ int fd;
+
+ memset(&stat_src, 0, sizeof(stat_src));
+
+ if (stat(filename, &stat_src) == -1) return -1;
+
+ if (S_ISBLK(stat_src.st_mode)) {
+ fd = open(filename, O_RDONLY);
+
+ if (fd == -1) return -1;
+
+ src_size = lseek(fd, 0, SEEK_END);
+ close(fd);
+ } else {
+ return -1;
+ }
+
+ return src_size;
+}
+
+int str2ll(char *s, long long *v) {
+ if (s == NULL || v == NULL) return 1;
+
+ char *end = NULL;
+ errno = 0;
+
+ *v = strtoll(s, &end, 10);
+
+ if (end == s || *end != '\0' || errno == ERANGE) return 1;
+ return 0;
+}
+
+void parse_args(int argc, char **argv, struct params_t *params) {
+ /*
+ * Allow to provide only one option but it has to be -h/--help.
+ * Show help and exit normally.
+ */
+ if (argc == 2 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) {
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+
+ /*
+ * Don't check if argc exceeds some value since same option can be
+ * provided in two forms, e.g. -c=N, -c N, --cnt N. It would be tedious
+ * to do such a check. Params will be validated later anyway and
+ * an error will be returned in case of unknown options.
+ */
+ if (argc < 3) {
+ usage(argv[0]);
+ exit(ERR_WRONG_ARGC);
+ }
+
+ int c, option_index;
+ off_t src_size, dst_size, min_size;
+ long long partsize;
+
+ params->tool_name = argv[0];
+ params->src = argv[1];
+ params->dst = argv[2];
+ params->cnt = -1;
+ params->partsize = DEFAULT_PARTSIZE;
+ params->update = 0;
+ params->progress_from = 0;
+ params->progress_to = 100;
+ params->sha1 = 0;
+
+ if (strcmp(params->src, params->dst) == 0) {
+ fprintf(stderr, "src and dst are the same file.\n");
+ exit(ERR_SAME_FILE);
+ }
+
+ if ((src_size = get_blockdev_size(params->src)) <= 0) {
+ fprintf(stderr, "Can't check src size or src is not a block device.\n");
+ exit(ERR_CHECK_SIZE);
+ }
+
+ if ((dst_size = get_blockdev_size(params->dst)) <= 0) {
+ fprintf(stderr, "Can't check dst size or dst is not a block device.\n");
+ exit(ERR_CHECK_SIZE);
+ }
+
+ min_size = (dst_size < src_size) ? dst_size : src_size;
+ opterr = 0; // getopt_long won't print to stderr
+
+ while (1) {
+ option_index = 0;
+ static struct option long_options[] = {
+ {"cnt", required_argument, 0, 'c' },
+ {"progress-from", required_argument, 0, 'f' },
+ {"progress-to", required_argument, 0, 't' },
+ {"help", no_argument, 0, 'h' },
+ {"update", no_argument, 0, 'u' },
+ {"partsize", required_argument, 0, 'p' },
+ {"sha1", no_argument, 0, 's' },
+ {0, 0, 0, 0 }
+ };
+
+ /*
+ * src and dst are argv[1] and argv[2].
+ * getopt_long has to process other args
+ */
+ c = getopt_long(argc - 2, argv + 2, "c:f:t:hup:",
+ long_options, &option_index);
+
+ if (c == -1) break;
+
+ switch (c) {
+ case 'c':
+ if (str2ll(optarg, ¶ms->cnt) != 0 || params->cnt <= 0 || params->cnt > min_size) {
+ fprintf(stderr, "Invalid cnt value.\n");
+ exit(ERR_WRONG_CNT);
+ }
+
+ break;
+ case 'f':
+ if (str2ll(optarg, ¶ms->progress_from) != 0 || params->progress_from < 0
+ || params->progress_from > params->progress_to) {
+ fprintf(stderr, "Invalid progress-from value.\n");
+ exit(ERR_WRONG_START);
+ }
+
+ break;
+ case 't':
+ if (str2ll(optarg, ¶ms->progress_to) != 0 || params->progress_to > 100
+ || params->progress_to < params->progress_from) {
+ fprintf(stderr, "Invalid progress-to value.\n");
+ exit(ERR_WRONG_STOP);
+ }
+
+ break;
+ case 'p':
+ if (str2ll(optarg, &partsize) != 0 || partsize <= 0
+ || partsize > MAX_PARTSIZE) {
+ fprintf(stderr, "Invalid partsize value.\n");
+ exit(ERR_WRONG_PARTSIZE);
+ } else {
+ params->partsize = (size_t) partsize;
+ }
+
+ break;
+ case 'h':
+ usage(argv[0]);
+ break;
+ case 'u':
+ params->update = 1;
+ break;
+ case 's':
+ params->sha1 = 1;
+ break;
+ default:
+ fprintf(stderr, "Unknown option.\n");
+ exit(ERR_WRONG_ARG);
+ }
+ }
+
+ if (params->cnt == -1) params->cnt = min_size;
+}
+
+void fd_cleanup(int *fd)
+{
+ if (!fd || *fd < 0)
+ return;
+ close(*fd);
+}
+
+int copy_and_update_progress(struct params_t *params) {
+ int prev_progress = params->progress_from;
+ int progress = params->progress_from;
+ ssize_t bytes_copied = 0;
+ long long left = params->cnt;
+ long long range = params->progress_to - params->progress_from;
+ size_t count = params->partsize;
+ double percentage;
+
+ __attribute__((cleanup(fd_cleanup))) int fd_src = open(params->src, O_RDONLY);
+
+ if (fd_src == -1) {
+ fprintf(stderr, "Cannot open src.\n");
+ return ERR_SRC_OPEN;
+ }
+
+ __attribute__((cleanup(fd_cleanup))) int fd_dst = open(params->dst, O_WRONLY);
+
+ if (fd_dst == -1) {
+ fprintf(stderr, "Cannot open dst.\n");
+ return ERR_DST_OPEN;
+ }
+
+ posix_fadvise(fd_src, 0, 0, POSIX_FADV_SEQUENTIAL);
+ posix_fadvise(fd_dst, 0, 0, POSIX_FADV_SEQUENTIAL);
+
+ fprintf(stderr, "%s -> %s: %3d%% (%3d%% overall)", params->src, params->dst, 0, (int) params->progress_from);
+
+ while(left > 0) {
+ count = (left > (long long)params->partsize) ? params->partsize : (size_t)left;
+ bytes_copied = sendfile(fd_dst, fd_src, 0, count);
+
+ if (bytes_copied == -1) {
+ fprintf(stderr, "Error during copying.\n");
+ return ERR_SENDFILE;
+ }
+
+ left -= bytes_copied;
+
+ if (range == 0) continue;
+
+ percentage = 100.0 * ((double) (params->cnt - left) / params->cnt);
+ progress = params->progress_from + percentage * ((double) range / 100.0);
+
+ if (progress - prev_progress > 0) {
+ #ifndef HOST_BUILD
+ if (params->update && hal_device_board_set_upgrade_status(progress) < 0) {
+ fprintf(stderr, "Cannot set upgrade status.\n");
+ return ERR_STATUS;
+ };
+ #endif
+
+ fprintf(stderr, "\r%s -> %s: %3d%% (%3d%% overall)", params->src, params->dst, (int) percentage, progress);
+ prev_progress = progress;
+ }
+ }
+
+ #ifndef HOST_BUILD
+ if (params->update && hal_device_board_set_upgrade_status(params->progress_to) < 0) {
+ fprintf(stderr, "Cannot set upgrade status.\n");
+ return ERR_STATUS;
+ }
+ #endif
+
+ fprintf(stderr, "\r%s -> %s: %3d%% (%3d%% overall)\n", params->src, params->dst, 100, (int) params->progress_to);
+ return 0;
+}
+
+int calculate_sha1(const char *dest, unsigned char *sha1, size_t read_bytes) {
+ __attribute__((cleanup(fd_cleanup))) int dest_fd = open(dest, O_RDONLY);
+ if (dest_fd == -1) return -1;
+
+ SHA1_CTX context;
+ SHA1Init(&context);
+
+ if (read_bytes == 0) read_bytes = SIZE_MAX;
+
+ while (read_bytes > 0) {
+ // This size doesn't really matter, so let's just pick a value that is big enough
+ // to not use too many syscalls.
+ unsigned char buf[1 << 16];
+
+ int r_read = read(dest_fd, buf, read_bytes < sizeof(buf) ? read_bytes : sizeof(buf));
+ read_bytes -= r_read;
+ switch (r_read) {
+ case -1:
+ return -1;
+ case 0:
+ // We read all the data. Let's check the results!
+ // ... wait, there is no two-layer (switch and while) break in C.
+ // Ugh, we have to goto instead.
+ goto while_done;
+ }
+
+ SHA1Update(&context, buf, r_read);
+ }
+while_done:;
+
+ SHA1Final(sha1, &context);
+ return 0;
+}
+
+int check_sha1(struct params_t *params) {
+ int ret = 0;
+ unsigned char sha1_src[20];
+ unsigned char sha1_dst[20];
+
+ if ((ret = calculate_sha1(params->src, sha1_src, params->cnt) != 0)) {
+ fprintf(stderr, "Error during SHA1 checksum calculation of %s.\n", params->src);
+ return ERR_SHA1_SRC;
+ };
+
+ if ((ret = calculate_sha1(params->dst, sha1_dst, params->cnt) != 0)) {
+ fprintf(stderr, "Error during SHA1 checksum calculation of %s.\n", params->dst);
+ return ERR_SHA1_DST;
+ };
+
+ int match = 1;
+
+ for (int i = 0; i < 20; ++i) {
+ if (sha1_src[i] != sha1_dst[i]) {
+ match = 0;
+ break;
+ }
+ }
+
+ if (match != 1) {
+ fprintf(stderr, "SHA1 checksums of %s and %s after copying do not match.\n", params->src, params->dst);
+ return ERR_SHA1_COMP;
+ }
+
+ fprintf(stderr, "SHA1 checksums of %s and %s match.\n", params->src, params->dst);
+
+ return 0;
+}
--- /dev/null
+/* Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: Apache-2.0 */
+
+#pragma once
+
+#include <sys/types.h>
+#include <time.h>
+
+#define NS_PER_SECOND 1000000000
+#define DEFAULT_PARTSIZE 1048576
+#define MAX_PARTSIZE 2147479552
+
+#define ERR_WRONG_ARGC 1
+#define ERR_SAME_FILE 2
+#define ERR_WRONG_CNT 3
+#define ERR_WRONG_START 4
+#define ERR_WRONG_STOP 5
+#define ERR_WRONG_PARTSIZE 6
+#define ERR_WRONG_ARG 7
+#define ERR_SRC_OPEN 8
+#define ERR_DST_OPEN 9
+#define ERR_SENDFILE 10
+#define ERR_CHECK_SIZE 11
+#define ERR_SHA1_SRC 12
+#define ERR_SHA1_DST 13
+#define ERR_SHA1_COMP 14
+#define ERR_STATUS 15
+
+/*
+ * Rationale for using long long values and strtoll instead of
+ * unsigned long long and strtoull to parse ranges which can't
+ * be negative:
+ *
+ * strtoull interprets '-' character at the beginning
+ * of a string as a valid character and negates the given value.
+ * We don't want such a behaviour and want to interpret '-' as
+ * an illegal value. Other than that, negative value of size can
+ * be interpreted as an error in certain functions.
+ */
+
+struct params_t {
+ char *tool_name;
+ char *src; // src block device
+ char *dst; // dst block device
+ long long cnt; // bytes to copy
+ int update; // update status or not
+ size_t partsize; // size of part to copy using sendfile
+ long long progress_from; // update status starting from this value
+ long long progress_to; // update status up to this value
+ int sha1; // calculate and compare sha1 or not
+};
+
+/*
+ * Print usage. If message != NULL, print message instead.
+ */
+void usage(char *name);
+
+/*
+ * Get size of a block device. Return error if cannot check size
+ * or not a block device.
+ */
+off_t get_blockdev_size(char *filename);
+
+/*
+ * Interpret string as long long and handle errors.
+ */
+int str2ll(char *s, long long *v);
+
+/*
+ * Parse command line arguments. Calls exit(status)
+ * if arguments are invalid.
+ */
+void parse_args(int argc, char **argv, struct params_t *params);
+
+/*
+ * Copy block device from one to another and update status of progress
+ */
+int copy_and_update_progress(struct params_t *params);
+
+void fd_cleanup(int *fd);
+
+int calculate_sha1(const char *dest, unsigned char *sha1, size_t read_bytes);
+
+int check_sha1(struct params_t *params);
--- /dev/null
+/* Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: Apache-2.0 */
+
+#include <stdio.h>
+#include <string.h>
+#include "lib.h"
+
+int main(int argc, char **argv) {
+ struct params_t params;
+ int ret;
+ /*
+ * Parse command line arguments. Call exit(status)
+ * if arguments are invalid.
+ */
+ parse_args(argc, argv, ¶ms);
+ ret = copy_and_update_progress(¶ms);
+
+ if (ret != 0) return ret;
+
+ if (params.sha1) ret = check_sha1(¶ms);
+
+ return ret;
+}