erofs-utils: mkfs: improvement for unprivileged container support

When developers want to use erofs as guest container rootfs, it
requires to uid/gid offsetting for each files.
This patch adds uid/gid offsetting feature to mkfs.erofs.

Example of how to use uid/gid offset:
 In case of lxc guest image.

 Image creation:
     mkafs.erofs --uid-offset=100000 --gid-offset=100000 file dir

 Set lxc config:
     lxc.idmap = u 0 100000 65536
     lxc.idmap = g 0 100000 65536

Signed-off-by: Naoto Yamaguchi <naoto.yamaguchi@aisin.co.jp>
Link: https://lore.kernel.org/r/20220814022915.7964-1-naoto.yamaguchi@aisin.co.jp
Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
diff --git a/include/erofs/config.h b/include/erofs/config.h
index 6c6d71f..539d813 100644
--- a/include/erofs/config.h
+++ b/include/erofs/config.h
@@ -68,6 +68,7 @@
 	u64 c_unix_timestamp;
 	u32 c_uid, c_gid;
 	const char *mount_point;
+	long long c_uid_offset, c_gid_offset;
 #ifdef WITH_ANDROID
 	char *target_out_path;
 	char *fs_config_file;
diff --git a/lib/inode.c b/lib/inode.c
index ce75014..4da28b3 100644
--- a/lib/inode.c
+++ b/lib/inode.c
@@ -855,6 +855,15 @@
 	inode->i_mode = st->st_mode;
 	inode->i_uid = cfg.c_uid == -1 ? st->st_uid : cfg.c_uid;
 	inode->i_gid = cfg.c_gid == -1 ? st->st_gid : cfg.c_gid;
+
+	if (inode->i_uid + cfg.c_uid_offset < 0)
+		erofs_err("uid overflow @ %s", path);
+	inode->i_uid += cfg.c_uid_offset;
+
+	if (inode->i_gid + cfg.c_gid_offset < 0)
+		erofs_err("gid overflow @ %s", path);
+	inode->i_gid += cfg.c_gid_offset;
+
 	inode->i_mtime = st->st_mtime;
 	inode->i_mtime_nsec = ST_MTIM_NSEC(st);
 
diff --git a/mkfs/main.c b/mkfs/main.c
index deb8e1f..b969b35 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -51,6 +51,8 @@
 	{"blobdev", required_argument, NULL, 13},
 	{"ignore-mtime", no_argument, NULL, 14},
 	{"preserve-mtime", no_argument, NULL, 15},
+	{"uid-offset", required_argument, NULL, 16},
+	{"gid-offset", required_argument, NULL, 17},
 	{"mount-point", required_argument, NULL, 512},
 #ifdef WITH_ANDROID
 	{"product-out", required_argument, NULL, 513},
@@ -97,6 +99,8 @@
 #endif
 	      " --force-uid=#         set all file uids to # (# = UID)\n"
 	      " --force-gid=#         set all file gids to # (# = GID)\n"
+	      " --uid-offset=#        add offset # to all file uids (# = id offset)\n"
+	      " --gid-offset=#        add offset # to all file gids (# = id offset)\n"
 	      " --help                display this help and exit\n"
 	      " --ignore-mtime        use build time instead of strict per-file modification time\n"
 	      " --max-extent-bytes=#  set maximum decompressed extent size # in bytes\n"
@@ -383,6 +387,22 @@
 		case 15:
 			cfg.c_ignore_mtime = false;
 			break;
+		case 16:
+			errno = 0;
+			cfg.c_uid_offset = strtoll(optarg, &endptr, 0);
+			if (errno || *endptr != '\0') {
+				erofs_err("invalid uid offset %s", optarg);
+				return -EINVAL;
+			}
+			break;
+		case 17:
+			errno = 0;
+			cfg.c_gid_offset = strtoll(optarg, &endptr, 0);
+			if (errno || *endptr != '\0') {
+				erofs_err("invalid gid offset %s", optarg);
+				return -EINVAL;
+			}
+			break;
 		case 1:
 			usage();
 			exit(0);