[][OpenWrt Dev][Add dual-boot features for fstools]

[Description]
Add a new feature for fstools that disables splitting rootfs_data partition
from rootfs partition for rootdisk-type devices.
Also add partition key parsing support (PARTUUID=/PARTLABEL=)

[Release-log]
N/A

Change-Id: Id57707c9b586fc5acdca416e114bdd4dbeadd0f4
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/5595597
diff --git a/openwrt_patches-21.02/120-fstools-split-libblkid-tiny-as-dependency.patch b/openwrt_patches-21.02/120-fstools-split-libblkid-tiny-as-dependency.patch
new file mode 100644
index 0000000..227c3c0
--- /dev/null
+++ b/openwrt_patches-21.02/120-fstools-split-libblkid-tiny-as-dependency.patch
@@ -0,0 +1,50 @@
+--- a/package/system/fstools/Makefile
++++ b/package/system/fstools/Makefile
+@@ -36,10 +36,16 @@ CMAKE_OPTIONS += $(if $(CONFIG_FSTOOLS_UBIFS_EXTROOT),-DCMAKE_UBIFS_EXTROOT=y)
+ CMAKE_OPTIONS += $(if $(CONFIG_FSTOOLS_OVL_MOUNT_FULL_ACCESS_TIME),-DCMAKE_OVL_MOUNT_FULL_ACCESS_TIME=y)
+ CMAKE_OPTIONS += $(if $(CONFIG_FSTOOLS_OVL_MOUNT_COMPRESS_ZLIB),-DCMAKE_OVL_MOUNT_COMPRESS_ZLIB=y)
+ 
++define Package/libfstools-bootparam
++  SECTION:=libs
++  CATEGORY:=Libraries
++  TITLE:=Boot parameter library for OpenWrt filesystem tools
++endef
++
+ define Package/fstools
+   SECTION:=base
+   CATEGORY:=Base system
+-  DEPENDS:=+ubox +USE_GLIBC:librt +NAND_SUPPORT:ubi-utils
++  DEPENDS:=+ubox +USE_GLIBC:librt +NAND_SUPPORT:ubi-utils +libfstools-bootparam
+   TITLE:=OpenWrt filesystem tools
+   MENU:=1
+ endef
+@@ -79,7 +85,7 @@ define Package/block-mount
+   SECTION:=base
+   CATEGORY:=Base system
+   TITLE:=Block device mounting and checking
+-  DEPENDS:=+ubox +libubox +libuci +libblobmsg-json +libjson-c
++  DEPENDS:=+ubox +libubox +libuci +libblobmsg-json +libjson-c +libfstools-bootparam
+ endef
+ 
+ define Package/blockd
+@@ -89,6 +95,12 @@ define Package/blockd
+   DEPENDS:=+block-mount +fstools +libubus +kmod-fs-autofs4 +libblobmsg-json +libjson-c
+ endef
+ 
++define Package/libfstools-bootparam/install
++	$(INSTALL_DIR) $(1)/lib
++
++	$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/lib/libfstools-bootparam.so $(1)/lib/
++endef
++
+ define Package/fstools/install
+ 	$(INSTALL_DIR) $(1)/sbin $(1)/lib
+ 
+@@ -132,6 +144,7 @@ define Build/InstallDev
+ 	$(CP) $(PKG_INSTALL_DIR)/usr/lib/libubi-utils.a $(1)/usr/lib/
+ endef
+ 
++$(eval $(call BuildPackage,libfstools-bootparam))
+ $(eval $(call BuildPackage,fstools))
+ $(eval $(call BuildPackage,snapshot-tool))
+ $(eval $(call BuildPackage,block-mount))
diff --git a/package/system/fstools/patches/0001-add-support-for-dual-boot.patch b/package/system/fstools/patches/0001-add-support-for-dual-boot.patch
new file mode 100644
index 0000000..a4a9d6c
--- /dev/null
+++ b/package/system/fstools/patches/0001-add-support-for-dual-boot.patch
@@ -0,0 +1,574 @@
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -5,6 +5,10 @@ ADD_DEFINITIONS(-Os -ggdb -Wall -Werror
+ 
+ SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
+ 
++ADD_LIBRARY(fstools-bootparam SHARED
++	boot_param.c)
++INSTALL(TARGETS fstools-bootparam LIBRARY DESTINATION lib)
++
+ ADD_LIBRARY(fstools SHARED
+ 		libfstools/snapshot.c
+ 		libfstools/extroot.c
+@@ -15,7 +19,7 @@ ADD_LIBRARY(fstools SHARED
+ 		libfstools/ubi.c
+ 		libfstools/rootdisk.c
+ 		libfstools/find.c)
+-TARGET_LINK_LIBRARIES(fstools ubox)
++TARGET_LINK_LIBRARIES(fstools ubox fstools-bootparam)
+ INSTALL(TARGETS fstools LIBRARY DESTINATION lib)
+ 
+ ADD_LIBRARY(blkid-tiny SHARED
+@@ -75,9 +79,9 @@ INSTALL(TARGETS blockd RUNTIME DESTINATI
+ ADD_EXECUTABLE(block block.c probe.c probe-libblkid.c)
+ IF(DEFINED CMAKE_UBIFS_EXTROOT)
+ 	ADD_DEFINITIONS(-DUBIFS_EXTROOT)
+-	TARGET_LINK_LIBRARIES(block blkid-tiny dl uci ubox ubus blobmsg_json ubi-utils ${json})
++	TARGET_LINK_LIBRARIES(block blkid-tiny fstools-bootparam dl uci ubox ubus blobmsg_json ubi-utils ${json})
+ ELSE(DEFINED CMAKE_UBIFS_EXTROOT)
+-	TARGET_LINK_LIBRARIES(block blkid-tiny dl uci ubox ubus blobmsg_json ${json})
++	TARGET_LINK_LIBRARIES(block blkid-tiny fstools-bootparam dl uci ubox ubus blobmsg_json ${json})
+ ENDIF(DEFINED CMAKE_UBIFS_EXTROOT)
+ INSTALL(TARGETS block RUNTIME DESTINATION sbin)
+ 
+--- a/block.c
++++ b/block.c
+@@ -47,6 +47,7 @@
+ #include <libubus.h>
+ 
+ #include "probe.h"
++#include "boot_param.h"
+ 
+ #define AUTOFS_MOUNT_PATH       "/tmp/run/blockd/"
+ 
+@@ -89,6 +90,9 @@ static LIST_HEAD(devices);
+ static int anon_mount, anon_swap, auto_mount, auto_swap, check_fs;
+ static unsigned int delay_root;
+ 
++static char *hide_block_devs[3];
++static uint32_t num_hide_block_devs;
++
+ enum {
+ 	CFG_ANON_MOUNT,
+ 	CFG_ANON_SWAP,
+@@ -498,9 +502,12 @@ static struct probe_info* _probe_path(ch
+ 	return probe_path(path);
+ }
+ 
++static char* find_mount_point(char *block);
++
+ static int _cache_load(const char *path)
+ {
+ 	int gl_flags = GLOB_NOESCAPE | GLOB_MARK;
++	uint32_t i;
+ 	int j;
+ 	glob_t gl;
+ 
+@@ -509,8 +516,30 @@ static int _cache_load(const char *path)
+ 
+ 	for (j = 0; j < gl.gl_pathc; j++) {
+ 		struct probe_info *pr = _probe_path(gl.gl_pathv[j]);
+-		if (pr)
++		bool skip_curr = false;
++
++		if (pr) {
++			char *mp = find_mount_point(pr->dev);
++			if (mp) {
++				/* Skip blocks mounted as root or overlay */
++				if (!strcmp(mp, "/rom") ||
++				    !strcmp(mp, "/overlay"))
++					continue;
++			}
++
++			for (i = 0; i < num_hide_block_devs; i++) {
++				/* Skip blocks used for dual boot */
++				if (!strcmp(hide_block_devs[i], pr->dev)) {
++					skip_curr = true;
++					break;
++				}
++			}
++
++			if (skip_curr)
++				continue;
++
+ 			list_add_tail(&pr->list, &devices);
++		}
+ 	}
+ 
+ 	globfree(&gl);
+@@ -1801,6 +1830,26 @@ static int main_swapoff(int argc, char *
+ 	return 0;
+ }
+ 
++static bool add_hide_block_dev(char *path)
++{
++	if (num_hide_block_devs >= ARRAY_SIZE(hide_block_devs))
++		return false;
++
++	hide_block_devs[num_hide_block_devs++] = path;
++	return true;
++}
++
++static void hide_boot_param_dev(const char *name)
++{
++	char *path;
++
++	path = boot_param_get_dev(name);
++	if (path) {
++		if (!add_hide_block_dev(path))
++			free(path);
++	}
++}
++
+ int main(int argc, char **argv)
+ {
+ 	char *base = basename(*argv);
+@@ -1810,6 +1859,10 @@ int main(int argc, char **argv)
+ 	ulog_open(-1, -1, "block");
+ 	ulog_threshold(LOG_NOTICE);
+ 
++	hide_boot_param_dev("rootfs_data_part");
++	hide_boot_param_dev("boot_rootfs_part");
++	hide_boot_param_dev("upgrade_rootfs_part");
++
+ 	if (!strcmp(base, "swapon"))
+ 		return main_swapon(argc, argv);
+ 
+--- a/boot_param.c
++++ b/boot_param.c
+@@ -0,0 +1,270 @@
++/* SPDX-License-Identifier: BSD-3-Clause */
++/*
++ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
++ *
++ * Author: Weijie Gao <weijie.gao@mediatek.com>
++ */
++
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <stdio.h>
++#include <string.h>
++#include <dirent.h>
++#include <fcntl.h>
++#include <unistd.h>
++#include <glob.h>
++#include <dlfcn.h>
++
++#include <blkid/blkid.h>
++#include <libubox/ulog.h>
++#include "boot_param.h"
++
++#define ARRAY_SIZE(a)	(sizeof(a) / sizeof(a[0]))
++
++#define BOOT_PARAM_STR_MAX_LEN			256
++
++static struct {
++	bool loaded;
++	blkid_probe (*new_probe_from_filename)(const char *);
++	int (*do_safeprobe)(blkid_probe);
++	int (*probe_lookup_value)(blkid_probe, const char *, const char **, size_t *);
++	void (*free_probe)(blkid_probe);
++	int (*probe_enable_partitions)(blkid_probe, int);
++	int (*probe_set_partitions_flags)(blkid_probe, int);
++} libblkid = {};
++
++bool read_boot_param_bool(const char *name)
++{
++	char path[BOOT_PARAM_STR_MAX_LEN], val;
++	size_t len;
++	FILE *f;
++
++	snprintf(path, sizeof(path), "/sys/module/boot_param/parameters/%s",
++		 name);
++
++	f = fopen(path, "rb");
++	if (!f)
++		return false;
++
++	len = fread(&val, 1, 1, f);
++	fclose(f);
++
++	if (len != 1)
++		return false;
++
++	return val == 'Y';
++}
++
++int read_boot_param_string(const char *name, char *val, size_t maxsize)
++{
++	char path[BOOT_PARAM_STR_MAX_LEN];
++	size_t len;
++	FILE *f;
++
++	snprintf(path, sizeof(path), "/sys/module/boot_param/parameters/%s",
++		 name);
++
++	f = fopen(path, "rb");
++	if (!f) {
++		val[0] = 0;
++		return -1;
++	}
++
++	len = fread(val, 1, maxsize, f);
++	fclose(f);
++
++	while (len > 0) {
++		if (val[len - 1] != '\n' && val[len - 1] != '\r')
++			break;
++
++		len--;
++	}
++
++	if (len < maxsize)
++		val[len] = 0;
++
++	return len;
++}
++
++int write_boot_param_string(const char *name, const char *val)
++{
++	size_t wlen, len = strlen(val);
++	char path[BOOT_PARAM_STR_MAX_LEN];
++	FILE *f;
++
++	if (len >= BOOT_PARAM_STR_MAX_LEN)
++		return -1;
++
++	snprintf(path, sizeof(path), "/sys/module/boot_param/parameters/%s",
++		 name);
++
++	f = fopen(path, "wb");
++	if (!f)
++		return -1;
++
++	wlen = fwrite(val, 1, len, f);
++	fclose(f);
++
++	return wlen;
++}
++
++static bool load_libblkid(void)
++{
++	void *lib;
++
++	if (libblkid.loaded)
++		return true;
++
++	lib = dlopen("libblkid.so", RTLD_GLOBAL);
++
++	if (!lib)
++		lib = dlopen("libblkid.so.1", RTLD_GLOBAL);
++
++	if (!lib)
++		return false;
++
++	libblkid.new_probe_from_filename = dlsym(lib, "blkid_new_probe_from_filename");
++	if (!libblkid.new_probe_from_filename)
++		return false;
++
++	libblkid.do_safeprobe = dlsym(lib, "blkid_do_safeprobe");
++	if (!libblkid.do_safeprobe)
++		return false;
++
++	libblkid.probe_lookup_value = dlsym(lib, "blkid_probe_lookup_value");
++	if (!libblkid.probe_lookup_value)
++		return false;
++
++	libblkid.free_probe = dlsym(lib, "blkid_free_probe");
++	if (!libblkid.free_probe)
++		return false;
++
++	libblkid.probe_enable_partitions = dlsym(lib, "blkid_probe_enable_partitions");
++	if (!libblkid.probe_enable_partitions)
++		return false;
++
++	libblkid.probe_set_partitions_flags = dlsym(lib, "blkid_probe_set_partitions_flags");
++	if (!libblkid.probe_set_partitions_flags)
++		return false;
++
++	libblkid.loaded = true;
++	return true;
++}
++
++static char *lookup_block_dev(const char *path, const char *key, bool is_uuid)
++{
++	int gl_flags = GLOB_NOESCAPE | GLOB_MARK;
++	const char *type, *value;
++	char *result = NULL;
++	size_t len;
++	glob_t gl;
++	int i;
++
++	if (glob(path, gl_flags, NULL, &gl) < 0)
++		return NULL;
++
++	type = is_uuid ? "PART_ENTRY_UUID" : "PART_ENTRY_NAME";
++
++	for (i = 0; i < gl.gl_pathc; i++) {
++		blkid_probe pr = libblkid.new_probe_from_filename(gl.gl_pathv[i]);
++		if (!pr)
++			continue;
++
++		libblkid.probe_enable_partitions(pr, 1);
++		libblkid.probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
++
++		if (libblkid.do_safeprobe(pr))
++			goto free_pr;
++
++		if (!libblkid.probe_lookup_value(pr, type, &value, &len)) {
++			if (!strcmp(value, key))
++				result = strdup(gl.gl_pathv[i]);
++		}
++
++	free_pr:
++		libblkid.free_probe(pr);
++
++		if (result)
++			break;
++	}
++
++	globfree(&gl);
++
++	return result;
++}
++
++static char *find_block_dev(const char *key, bool is_uuid)
++{
++	char *devpath = NULL;
++	int i;
++
++	static const char *block_pats[] = {
++		"/dev/loop*",
++		"/dev/mmcblk*",
++		"/dev/sd*",
++		"/dev/hd*",
++		"/dev/md*",
++		"/dev/nvme*",
++		"/dev/vd*",
++		"/dev/xvd*",
++		"/dev/mapper/*",
++	};
++
++	if (!load_libblkid())
++		return NULL;
++
++	for (i = 0; i < ARRAY_SIZE(block_pats); i++) {
++		devpath = lookup_block_dev(block_pats[i], key, is_uuid);
++		if (devpath)
++			break;
++	}
++
++	return devpath;
++}
++
++char *blockdev_parse(const char *name)
++{
++	char *e, *part_dev_path;
++	struct stat st;
++
++	if (!name)
++		return NULL;
++
++	e = strchr(name, '=');
++	if (e) {
++		*e = 0;
++		e++;
++	}
++
++	if (!e) {
++		if (stat(name, &st))
++			return NULL;
++
++		if (!S_ISBLK(st.st_mode))
++			return NULL;
++
++		part_dev_path = strdup(name);
++	} else if (!strcmp(name, "PARTLABEL")) {
++		part_dev_path = find_block_dev(e, false);
++	} else if (!strcmp(name, "PARTUUID")) {
++		if (strlen(e) != 36)
++			return NULL;
++		part_dev_path = find_block_dev(e, true);
++	} else {
++		return NULL;
++	}
++
++	return part_dev_path;
++}
++
++char *boot_param_get_dev(const char *name)
++{
++	char partkey[BOOT_PARAM_STR_MAX_LEN];
++
++	read_boot_param_string(name, partkey, sizeof(partkey));
++
++	if (!partkey[0])
++		return NULL;
++
++	return blockdev_parse(partkey);
++}
+--- a/boot_param.h
++++ b/boot_param.h
+@@ -0,0 +1,21 @@
++// SPDX-License-Identifier: BSD-3-Clause
++/*
++ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
++ *
++ * Author: Weijie Gao <weijie.gao@mediatek.com>
++ */
++
++#ifndef _BOOT_PARAM_H_
++#define _BOOT_PARAM_H_
++
++#include <stddef.h>
++#include <stdbool.h>
++
++bool read_boot_param_bool(const char *name);
++int read_boot_param_string(const char *name, char *val, size_t maxsize);
++int write_boot_param_string(const char *name, const char *val);
++
++char *blockdev_parse(const char *name);
++char *boot_param_get_dev(const char *name);
++
++#endif /* _BOOT_PARAM_H_ */
+--- a/libfstools/rootdisk.c
++++ b/libfstools/rootdisk.c
+@@ -26,6 +26,7 @@
+ 
+ #include "libfstools.h"
+ #include "volume.h"
++#include "../boot_param.h"
+ 
+ #include <linux/loop.h>
+ 
+@@ -42,6 +43,7 @@ struct rootdev_volume {
+ 	struct volume v;
+ 	uint64_t offset;
+ 	char loop_name[32];
++	char *dev_path;
+ };
+ 
+ static const char *rootdev;
+@@ -109,11 +111,15 @@ static int get_squashfs(struct squashfs_
+ 
+ static bool rootdisk_use_f2fs(struct rootdev_volume *p)
+ {
++	const char *dev = rootdev;
+ 	uint64_t size = 0;
+ 	bool ret = false;
+ 	int fd;
+ 
+-	fd = open(rootdev, O_RDONLY);
++	if (p->dev_path)
++		dev = p->dev_path;
++
++	fd = open(dev, O_RDONLY);
+ 	if (ioctl(fd, BLKGETSIZE64, &size) == 0)
+ 		ret = size - p->offset > F2FS_MINSIZE;
+ 	close(fd);
+@@ -121,6 +127,30 @@ static bool rootdisk_use_f2fs(struct roo
+ 	return ret;
+ }
+ 
++static struct volume *find_existed_rootfs_data(void)
++{
++	struct rootdev_volume *p;
++	char *rootfs_data_dev;
++
++	rootfs_data_dev = boot_param_get_dev("rootfs_data_part");
++
++	if (!rootfs_data_dev)
++		return NULL;
++
++	ULOG_NOTE("Using existed rootfs_data device %s\n", rootfs_data_dev);
++
++	write_boot_param_string("rootfs_data_part", rootfs_data_dev);
++
++	p = calloc(1, sizeof(*p));
++	p->v.drv = &rootdisk_driver;
++	p->v.name = "rootfs_data";
++
++	p->offset = 0;
++	p->dev_path = rootfs_data_dev;
++
++	return &p->v;
++}
++
+ static struct volume *rootdisk_volume_find(char *name)
+ {
+ 	struct squashfs_super_block sb;
+@@ -129,6 +159,9 @@ static struct volume *rootdisk_volume_fi
+ 	if (strcmp(name, "rootfs_data") != 0)
+ 		return NULL;
+ 
++	if (read_boot_param_bool("no_split_rootfs_data"))
++		return find_existed_rootfs_data();
++
+ 	if (!rootdev)
+ 		rootdev = get_rootdev("/");
+ 	if (!rootdev)
+@@ -160,12 +193,16 @@ static struct volume *rootdisk_volume_fi
+ static int rootdisk_volume_identify(struct volume *v)
+ {
+ 	struct rootdev_volume *p = container_of(v, struct rootdev_volume, v);
++	const char *dev = rootdev;
+ 	int ret = FS_NONE;
+ 	uint32_t magic = 0;
+ 	size_t n;
+ 	FILE *f;
+ 
+-	f = fopen(rootdev, "r");
++	if (p->dev_path)
++		dev = p->dev_path;
++
++	f = fopen(dev, "r");
+ 	if (!f)
+ 		return ret;
+ 
+@@ -265,6 +302,13 @@ static int rootdisk_volume_init(struct v
+ 	char str[128];
+ 	int ret = 0;
+ 
++	if (p->dev_path) {
++		/* Do not create loop device with no_split_rootfs_data set */
++		v->type = BLOCKDEV;
++		v->blk = p->dev_path;
++		goto do_format;
++	}
++
+ 	if (!p->loop_name[0] && rootdisk_create_loop(p) != 0) {
+ 		ULOG_ERR("unable to create loop device\n");
+ 		return -1;
+@@ -273,6 +317,7 @@ static int rootdisk_volume_init(struct v
+ 	v->type = BLOCKDEV;
+ 	v->blk = p->loop_name;
+ 
++do_format:
+ 	switch (rootdisk_volume_identify(v)) {
+ 	case FS_NONE:
+ 		ULOG_INFO("rootdisk overlay filesystem has not been formatted yet\n");
+--- a/mount_root.c
++++ b/mount_root.c
+@@ -23,6 +23,8 @@
+ #include "libfstools/libfstools.h"
+ #include "libfstools/volume.h"
+ 
++#include "boot_param.h"
++
+ /*
+  * Called in the early (PREINIT) stage, when we immediately need some writable
+  * filesystem.
+@@ -58,6 +60,12 @@ start(int argc, char *argv[1])
+ 	/* There isn't extroot, so just try to mount "rootfs_data" */
+ 	volume_init(data);
+ 	switch (volume_identify(data)) {
++	case -1:
++		/* Use ramoverlay if no "rootfs_data" device found with no_split_rootfs_data set */
++		if (!read_boot_param_bool("no_split_rootfs_data"))
++			break;
++
++		/* fall through */
+ 	case FS_NONE:
+ 		ULOG_WARN("no usable overlay filesystem found, using tmpfs overlay\n");
+ 		return ramoverlay();
diff --git a/prepare_sdk.sh b/prepare_sdk.sh
index 16c2ae1..ae48710 100755
--- a/prepare_sdk.sh
+++ b/prepare_sdk.sh
@@ -32,6 +32,7 @@
 sdk_patch
 #cp mtk target to OpenWRT
 cp -fpR ${MTK_FEEDS_DIR}/target ./
+cp -fpR ${MTK_FEEDS_DIR}/package ./
 #remove patch if choose to not "keep" patch
 if [ -z ${2} ]; then
 	remove_patches