lxc: fix huge binary sizes by backporting upstream Meson dynlink fixes
authorPetr Štetiar <[email protected]>
Mon, 29 Jul 2024 18:04:06 +0000 (18:04 +0000)
committerJosef Schlehofer <[email protected]>
Wed, 21 Aug 2024 14:14:41 +0000 (16:14 +0200)
LXC after the switch to the Meson build system increased the binary sizes
significantly as each binary is basically static so shipping complete
liblxc which should be linked dynamically.

Upstream later fixed it with series of 10 commits and this fixes are
available in LXC release v6.0.0. Since we can't upstep to that release,
lets fix it by backporting those fixes only, basically making libxlc a
shared library again.

Package sizes before:

 384K lxc-user-nic_5.0.3-1_aarch64_cortex-a53.ipk
 383K lxc-ls_5.0.3-1_aarch64_cortex-a53.ipk
 382K lxc-top_5.0.3-1_aarch64_cortex-a53.ipk
 382K lxc-copy_5.0.3-1_aarch64_cortex-a53.ipk
 381K lxc-unshare_5.0.3-1_aarch64_cortex-a53.ipk
 380K lxc-start_5.0.3-1_aarch64_cortex-a53.ipk
 380K lxc-monitor_5.0.3-1_aarch64_cortex-a53.ipk
 380K lxc-info_5.0.3-1_aarch64_cortex-a53.ipk
 380K lxc-create_5.0.3-1_aarch64_cortex-a53.ipk
 380K lxc-autostart_5.0.3-1_aarch64_cortex-a53.ipk
 380K lxc-attach_5.0.3-1_aarch64_cortex-a53.ipk
 379K lxc-execute_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-wait_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-usernsexec_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-unfreeze_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-stop_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-freeze_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-device_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-destroy_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-console_5.0.3-1_aarch64_cortex-a53.ipk
 378K lxc-cgroup_5.0.3-1_aarch64_cortex-a53.ipk
 376K liblxc_5.0.3-1_aarch64_cortex-a53.ipk
 375K lxc-config_5.0.3-1_aarch64_cortex-a53.ipk
 12K lxc-hooks_5.0.3-1_aarch64_cortex-a53.ipk
 11K lxc-templates_5.0.3-1_aarch64_cortex-a53.ipk
 3.7K lxc-checkconfig_5.0.3-1_aarch64_cortex-a53.ipk
 2.4K lxc-configs_5.0.3-1_aarch64_cortex-a53.ipk
 1.9K lxc-auto_5.0.3-1_aarch64_cortex-a53.ipk
 1.6K lxc-common_5.0.3-1_aarch64_cortex-a53.ipk
 1.2K lxc-unprivileged_5.0.3-1_aarch64_cortex-a53.ipk
 978 lxc_5.0.3-1_aarch64_cortex-a53.ipk

Sizes after:

 378K liblxc_5.0.3-2_aarch64_cortex-a53.ipk
 27K lxc-user-nic_5.0.3-2_aarch64_cortex-a53.ipk
 24K lxc-ls_5.0.3-2_aarch64_cortex-a53.ipk
 21K lxc-usernsexec_5.0.3-2_aarch64_cortex-a53.ipk
 21K lxc-top_5.0.3-2_aarch64_cortex-a53.ipk
 20K lxc-unshare_5.0.3-2_aarch64_cortex-a53.ipk
 20K lxc-copy_5.0.3-2_aarch64_cortex-a53.ipk
 20K lxc-attach_5.0.3-2_aarch64_cortex-a53.ipk
 18K lxc-start_5.0.3-2_aarch64_cortex-a53.ipk
 18K lxc-info_5.0.3-2_aarch64_cortex-a53.ipk
 18K lxc-execute_5.0.3-2_aarch64_cortex-a53.ipk
 18K lxc-device_5.0.3-2_aarch64_cortex-a53.ipk
 18K lxc-create_5.0.3-2_aarch64_cortex-a53.ipk
 18K lxc-autostart_5.0.3-2_aarch64_cortex-a53.ipk
 17K lxc-destroy_5.0.3-2_aarch64_cortex-a53.ipk
 16K lxc-wait_5.0.3-2_aarch64_cortex-a53.ipk
 16K lxc-unfreeze_5.0.3-2_aarch64_cortex-a53.ipk
 16K lxc-stop_5.0.3-2_aarch64_cortex-a53.ipk
 16K lxc-freeze_5.0.3-2_aarch64_cortex-a53.ipk
 16K lxc-console_5.0.3-2_aarch64_cortex-a53.ipk
 16K lxc-cgroup_5.0.3-2_aarch64_cortex-a53.ipk
 15K lxc-monitor_5.0.3-2_aarch64_cortex-a53.ipk
 13K lxc-config_5.0.3-2_aarch64_cortex-a53.ipk
 12K lxc-hooks_5.0.3-2_aarch64_cortex-a53.ipk
 11K lxc-templates_5.0.3-2_aarch64_cortex-a53.ipk
 3.7K lxc-checkconfig_5.0.3-2_aarch64_cortex-a53.ipk
 2.4K lxc-configs_5.0.3-2_aarch64_cortex-a53.ipk
 1.9K lxc-auto_5.0.3-2_aarch64_cortex-a53.ipk
 1.6K lxc-common_5.0.3-2_aarch64_cortex-a53.ipk
 1.1K lxc-unprivileged_5.0.3-2_aarch64_cortex-a53.ipk
 944 lxc_5.0.3-2_aarch64_cortex-a53.ipk

Sum of Package Sizes:

 Before: 8758.78K
 After:   814.64K

The total package size has decreased by approximately 90% after the fix.

References: https://github.com/lxc/lxc/pull/4401
Signed-off-by: Petr Štetiar <[email protected]>
utils/lxc/Makefile
utils/lxc/patches/001-confile-move-lxc_fill_elevated_privileges-to-tools-l.patch [new file with mode: 0644]
utils/lxc/patches/002-meson-introduce-IN_LIBLXC-preprocessor-macro.patch [new file with mode: 0644]
utils/lxc/patches/003-confile-unhide-lxc_config_define-helpers.patch [new file with mode: 0644]
utils/lxc/patches/004-conf-reorganize-split-code-to-idmap_utils.c.patch [new file with mode: 0644]
utils/lxc/patches/005-conf-reorganize-split-code-to-utils.c.patch [new file with mode: 0644]
utils/lxc/patches/006-confile-unhide-lxc_config_parse_arch-helper.patch [new file with mode: 0644]
utils/lxc/patches/007-storage_utils-unhide-and-rename-is_valid_storage_typ.patch [new file with mode: 0644]
utils/lxc/patches/008-storage_utils-move-get_fssize-to-utils.patch [new file with mode: 0644]
utils/lxc/patches/009-network-use-IN_LIBLXC.patch [new file with mode: 0644]
utils/lxc/patches/010-meson-link-with-liblxc-dynamically-everywhere-if-pos.patch [new file with mode: 0644]

index 55ae80b335c421ee128c8f1ced86ba722782e8e5..3865df8f1d29b60b99df54358823b5eeb3827836 100644 (file)
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
 
 PKG_NAME:=lxc
 PKG_VERSION:=5.0.3
-PKG_RELEASE:=1
+PKG_RELEASE:=2
 
 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
 PKG_SOURCE_URL:=https://linuxcontainers.org/downloads/lxc/
diff --git a/utils/lxc/patches/001-confile-move-lxc_fill_elevated_privileges-to-tools-l.patch b/utils/lxc/patches/001-confile-move-lxc_fill_elevated_privileges-to-tools-l.patch
new file mode 100644 (file)
index 0000000..ada9427
--- /dev/null
@@ -0,0 +1,144 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sat, 17 Feb 2024 16:43:21 +0100
+Subject: [PATCH] confile: move lxc_fill_elevated_privileges() to
+ tools/lxc_attach
+
+lxc_fill_elevated_privileges() is used only in lxc-attach tool,
+let's move this function in there.
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit 672b2172de2277e950e05d2abe0b1115fa6c3f53)
+---
+ src/lxc/confile.c          | 45 -------------------------------------
+ src/lxc/confile.h          |  2 --
+ src/lxc/tools/lxc_attach.c | 46 ++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 46 insertions(+), 47 deletions(-)
+
+--- a/src/lxc/confile.c
++++ b/src/lxc/confile.c
+@@ -3295,51 +3295,6 @@ int lxc_config_parse_arch(const char *ar
+       return ret_errno(EINVAL);
+ }
+-int lxc_fill_elevated_privileges(char *flaglist, unsigned int *flags)
+-{
+-      unsigned int flags_tmp = 0;
+-      char *token;
+-      struct {
+-              const char *token;
+-              int flag;
+-      } all_privs[] = {
+-              { "CGROUP", LXC_ATTACH_MOVE_TO_CGROUP    },
+-              { "CAP",    LXC_ATTACH_DROP_CAPABILITIES },
+-              { "LSM",    LXC_ATTACH_LSM_EXEC          },
+-              { NULL,     0                            }
+-      };
+-
+-      if (!flaglist) {
+-              /*
+-               * For the sake of backward compatibility, keep all privileges
+-               * if no specific privileges are specified.
+-               */
+-              for (unsigned int i = 0; all_privs[i].token; i++)
+-                      flags_tmp |= all_privs[i].flag;
+-
+-              *flags = flags_tmp;
+-              return 0;
+-      }
+-
+-      lxc_iterate_parts(token, flaglist, "|") {
+-              bool valid_token = false;
+-
+-              for (unsigned int i = 0; all_privs[i].token; i++) {
+-                      if (!strequal(all_privs[i].token, token))
+-                              continue;
+-
+-                      valid_token = true;
+-                      flags_tmp |= all_privs[i].flag;
+-              }
+-
+-              if (!valid_token)
+-                      return syserror_set(-EINVAL, "Invalid elevated privilege \"%s\" requested", token);
+-      }
+-
+-      *flags = flags_tmp;
+-      return 0;
+-}
+-
+ /* Write out a configuration file. */
+ int write_config(int fd, const struct lxc_conf *conf)
+ {
+--- a/src/lxc/confile.h
++++ b/src/lxc/confile.h
+@@ -90,8 +90,6 @@ __hidden extern void lxc_config_define_f
+  */
+ __hidden extern int lxc_config_parse_arch(const char *arch, signed long *persona);
+-__hidden extern int lxc_fill_elevated_privileges(char *flaglist, unsigned int *flags);
+-
+ __hidden extern int lxc_clear_config_item(struct lxc_conf *c, const char *key);
+ __hidden extern int write_config(int fd, const struct lxc_conf *conf);
+--- a/src/lxc/tools/lxc_attach.c
++++ b/src/lxc/tools/lxc_attach.c
+@@ -46,6 +46,7 @@ __attribute__((constructor)) static void
+ #endif
+ static int my_parser(struct lxc_arguments *args, int c, char *arg);
++static int lxc_fill_elevated_privileges(char *flaglist, unsigned int *flags);
+ static int add_to_simple_array(char ***array, ssize_t *capacity, char *value);
+ static bool stdfd_is_pty(void);
+ static int lxc_attach_create_log_file(const char *log_file);
+@@ -213,6 +214,51 @@ static int my_parser(struct lxc_argument
+       return 0;
+ }
++static int lxc_fill_elevated_privileges(char *flaglist, unsigned int *flags)
++{
++      unsigned int flags_tmp = 0;
++      char *token;
++      struct {
++              const char *token;
++              int flag;
++      } all_privs[] = {
++              { "CGROUP", LXC_ATTACH_MOVE_TO_CGROUP    },
++              { "CAP",    LXC_ATTACH_DROP_CAPABILITIES },
++              { "LSM",    LXC_ATTACH_LSM_EXEC          },
++              { NULL,     0                            }
++      };
++
++      if (!flaglist) {
++              /*
++               * For the sake of backward compatibility, keep all privileges
++               * if no specific privileges are specified.
++               */
++              for (unsigned int i = 0; all_privs[i].token; i++)
++                      flags_tmp |= all_privs[i].flag;
++
++              *flags = flags_tmp;
++              return 0;
++      }
++
++      lxc_iterate_parts(token, flaglist, "|") {
++              bool valid_token = false;
++
++              for (unsigned int i = 0; all_privs[i].token; i++) {
++                      if (!strequal(all_privs[i].token, token))
++                              continue;
++
++                      valid_token = true;
++                      flags_tmp |= all_privs[i].flag;
++              }
++
++              if (!valid_token)
++                      return syserror_set(-EINVAL, "Invalid elevated privilege \"%s\" requested", token);
++      }
++
++      *flags = flags_tmp;
++      return 0;
++}
++
+ static int add_to_simple_array(char ***array, ssize_t *capacity, char *value)
+ {
+       ssize_t count = 0;
diff --git a/utils/lxc/patches/002-meson-introduce-IN_LIBLXC-preprocessor-macro.patch b/utils/lxc/patches/002-meson-introduce-IN_LIBLXC-preprocessor-macro.patch
new file mode 100644 (file)
index 0000000..9f2e886
--- /dev/null
@@ -0,0 +1,78 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sat, 17 Feb 2024 16:47:41 +0100
+Subject: [PATCH] meson: introduce IN_LIBLXC preprocessor macro
+
+The purpose of it is to tell us if we are compiling
+liblxc or lxc test/tool/command.
+
+This thing is needed to exclude unnecessary functions
+from being compiled-in in the resulting executables
+like lxc-start, lxc-attach, etc.
+
+The problem is that lxc tools (lxc-start, lxc-stop, etc)
+depend not only on the liblxc as a shared library, but also
+require some non-exported symbols or helpers from liblxc
+internals. So, we have to link these executables with some liblxc
+object files directly which results in the dependency hell,
+because linking one .c file from liblxc may end up having to
+link with another one (what contains some dependency) and so on.
+By using IN_LIBLXC in the liblxc internals we can selectively
+omit some functions from being compiled in such cases.
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit f14656ebf62ab0dbb836431e2781cfd363f4e4aa)
+---
+ src/lxc/meson.build | 2 +-
+ src/lxc/state.c     | 4 ++++
+ src/lxc/utils.c     | 4 ++++
+ 3 files changed, 9 insertions(+), 1 deletion(-)
+
+--- a/src/lxc/meson.build
++++ b/src/lxc/meson.build
+@@ -157,7 +157,7 @@ liblxc_static = static_library(
+     install: true,
+     include_directories: liblxc_includes,
+     dependencies: [threads] + liblxc_dependency_headers,
+-    c_args: '-fvisibility=default')
++    c_args: ['-fvisibility=default', '-DIN_LIBLXC'])
+ lxc_functions = configure_file(
+     configuration: conf,
+--- a/src/lxc/state.c
++++ b/src/lxc/state.c
+@@ -52,6 +52,8 @@ lxc_state_t lxc_str2state(const char *st
+       return -1;
+ }
++#ifdef IN_LIBLXC
++
+ lxc_state_t lxc_getstate(const char *name, const char *lxcpath)
+ {
+       return lxc_cmd_get_state(name, lxcpath);
+@@ -117,3 +119,5 @@ int lxc_wait(const char *lxcname, const
+       return 0;
+ }
++
++#endif /* IN_LIBLXC */
+--- a/src/lxc/utils.c
++++ b/src/lxc/utils.c
+@@ -61,6 +61,8 @@ lxc_log_define(utils, lxc);
+  */
+ extern bool btrfs_try_remove_subvol(const char *path);
++#ifdef IN_LIBLXC
++
+ static int _recursive_rmdir(const char *dirname, dev_t pdev,
+                           const char *exclude, int level, bool onedev)
+ {
+@@ -193,6 +195,8 @@ extern int lxc_rmdir_onedev(const char *
+       return _recursive_rmdir(path, mystat.st_dev, exclude, 0, onedev);
+ }
++#endif /* IN_LIBLXC */
++
+ /* borrowed from iproute2 */
+ extern int get_u16(unsigned short *val, const char *arg, int base)
+ {
diff --git a/utils/lxc/patches/003-confile-unhide-lxc_config_define-helpers.patch b/utils/lxc/patches/003-confile-unhide-lxc_config_define-helpers.patch
new file mode 100644 (file)
index 0000000..186d886
--- /dev/null
@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sat, 17 Feb 2024 16:58:02 +0100
+Subject: [PATCH] confile: unhide lxc_config_define*() helpers
+
+Let's unhide lxc_config_define_add, lxc_config_define_load and
+lxc_config_define_free helpers. These functions are safe enough
+to be used by external tools. Semantic is also clear.
+
+Reason is that we have lxc-start/lxc-execute tools which
+use these symbols. Right now it works, because we just
+link a whole liblxc statically to each lxc-* tool...
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit b7591ad49d6051047371824bae6f19e37ba86dea)
+---
+ src/lxc/confile.h | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/src/lxc/confile.h
++++ b/src/lxc/confile.h
+@@ -77,11 +77,11 @@ __hidden extern int lxc_config_read(cons
+ __hidden extern int append_unexp_config_line(const char *line, struct lxc_conf *conf);
+-__hidden extern int lxc_config_define_add(struct lxc_list *defines, char *arg);
++extern int lxc_config_define_add(struct lxc_list *defines, char *arg);
+-__hidden extern bool lxc_config_define_load(struct lxc_list *defines, struct lxc_container *c);
++extern bool lxc_config_define_load(struct lxc_list *defines, struct lxc_container *c);
+-__hidden extern void lxc_config_define_free(struct lxc_list *defines);
++extern void lxc_config_define_free(struct lxc_list *defines);
+ #define LXC_ARCH_UNCHANGED 0xffffffffL
+ /*
diff --git a/utils/lxc/patches/004-conf-reorganize-split-code-to-idmap_utils.c.patch b/utils/lxc/patches/004-conf-reorganize-split-code-to-idmap_utils.c.patch
new file mode 100644 (file)
index 0000000..20eecc6
--- /dev/null
@@ -0,0 +1,939 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sun, 18 Feb 2024 15:17:25 +0100
+Subject: [PATCH] conf: reorganize/split code to idmap_utils.c
+
+Move some idmaps-related functions from lxc/conf.c
+to a new idmap_utils.c file.
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit 863c59dc3aca086a892ad44eb2dfa53438544944)
+---
+ src/lxc/cmd/lxc_usernsexec.c |   1 +
+ src/lxc/conf.c               | 371 +--------------------------------
+ src/lxc/conf.h               |   6 -
+ src/lxc/idmap_utils.c        | 391 +++++++++++++++++++++++++++++++++++
+ src/lxc/idmap_utils.h        |  30 +++
+ src/lxc/lxccontainer.c       |   1 +
+ src/lxc/meson.build          |   2 +
+ src/lxc/start.c              |   1 +
+ src/lxc/tools/lxc_unshare.c  |   1 +
+ 9 files changed, 428 insertions(+), 376 deletions(-)
+ create mode 100644 src/lxc/idmap_utils.c
+ create mode 100644 src/lxc/idmap_utils.h
+
+--- a/src/lxc/cmd/lxc_usernsexec.c
++++ b/src/lxc/cmd/lxc_usernsexec.c
+@@ -22,6 +22,7 @@
+ #include "compiler.h"
+ #include "conf.h"
+ #include "hlist.h"
++#include "idmap_utils.h"
+ #include "list.h"
+ #include "log.h"
+ #include "macro.h"
+--- a/src/lxc/conf.c
++++ b/src/lxc/conf.c
+@@ -41,6 +41,7 @@
+ #include "confile.h"
+ #include "confile_utils.h"
+ #include "error.h"
++#include "idmap_utils.h"
+ #include "log.h"
+ #include "lsm/lsm.h"
+ #include "lxclock.h"
+@@ -1664,35 +1665,6 @@ static int lxc_setup_rootfs_switch_root(
+       return lxc_pivot_root(rootfs);
+ }
+-static const struct id_map *find_mapped_nsid_entry(const struct lxc_conf *conf,
+-                                                 unsigned id,
+-                                                 enum idtype idtype)
+-{
+-      struct id_map *map;
+-      struct id_map *retmap = NULL;
+-
+-      /* Shortcut for container's root mappings. */
+-      if (id == 0) {
+-              if (idtype == ID_TYPE_UID)
+-                      return conf->root_nsuid_map;
+-
+-              if (idtype == ID_TYPE_GID)
+-                      return conf->root_nsgid_map;
+-      }
+-
+-      list_for_each_entry(map, &conf->id_map, head) {
+-              if (map->idtype != idtype)
+-                      continue;
+-
+-              if (id >= map->nsid && id < map->nsid + map->range) {
+-                      retmap = map;
+-                      break;
+-              }
+-      }
+-
+-      return retmap;
+-}
+-
+ static int lxc_recv_devpts_from_child(struct lxc_handler *handler)
+ {
+       int ret;
+@@ -3476,243 +3448,6 @@ struct lxc_conf *lxc_conf_init(void)
+       return new;
+ }
+-int write_id_mapping(enum idtype idtype, pid_t pid, const char *buf,
+-                   size_t buf_size)
+-{
+-      __do_close int fd = -EBADF;
+-      int ret;
+-      char path[PATH_MAX];
+-
+-      if (geteuid() != 0 && idtype == ID_TYPE_GID) {
+-              __do_close int setgroups_fd = -EBADF;
+-
+-              ret = strnprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
+-              if (ret < 0)
+-                      return -E2BIG;
+-
+-              setgroups_fd = open(path, O_WRONLY);
+-              if (setgroups_fd < 0 && errno != ENOENT)
+-                      return log_error_errno(-1, errno, "Failed to open \"%s\"", path);
+-
+-              if (setgroups_fd >= 0) {
+-                      ret = lxc_write_nointr(setgroups_fd, "deny\n",
+-                                             STRLITERALLEN("deny\n"));
+-                      if (ret != STRLITERALLEN("deny\n"))
+-                              return log_error_errno(-1, errno, "Failed to write \"deny\" to \"/proc/%d/setgroups\"", pid);
+-                      TRACE("Wrote \"deny\" to \"/proc/%d/setgroups\"", pid);
+-              }
+-      }
+-
+-      ret = strnprintf(path, sizeof(path), "/proc/%d/%cid_map", pid,
+-                     idtype == ID_TYPE_UID ? 'u' : 'g');
+-      if (ret < 0)
+-              return -E2BIG;
+-
+-      fd = open(path, O_WRONLY | O_CLOEXEC);
+-      if (fd < 0)
+-              return log_error_errno(-1, errno, "Failed to open \"%s\"", path);
+-
+-      ret = lxc_write_nointr(fd, buf, buf_size);
+-      if (ret < 0 || (size_t)ret != buf_size)
+-              return log_error_errno(-1, errno, "Failed to write %cid mapping to \"%s\"",
+-                                     idtype == ID_TYPE_UID ? 'u' : 'g', path);
+-
+-      return 0;
+-}
+-
+-/* Check whether a binary exist and has either CAP_SETUID, CAP_SETGID or both.
+- *
+- * @return  1      if functional binary was found
+- * @return  0      if binary exists but is lacking privilege
+- * @return -ENOENT if binary does not exist
+- * @return -EINVAL if cap to check is neither CAP_SETUID nor CAP_SETGID
+- */
+-static int idmaptool_on_path_and_privileged(const char *binary, cap_value_t cap)
+-{
+-      __do_free char *path = NULL;
+-      int ret;
+-      struct stat st;
+-
+-      if (cap != CAP_SETUID && cap != CAP_SETGID)
+-              return ret_errno(EINVAL);
+-
+-      path = on_path(binary, NULL);
+-      if (!path)
+-              return ret_errno(ENOENT);
+-
+-      ret = stat(path, &st);
+-      if (ret < 0)
+-              return -errno;
+-
+-      /* Check if the binary is setuid. */
+-      if (st.st_mode & S_ISUID)
+-              return log_debug(1, "The binary \"%s\" does have the setuid bit set", path);
+-
+-#if HAVE_LIBCAP && LIBCAP_SUPPORTS_FILE_CAPABILITIES
+-      /* Check if it has the CAP_SETUID capability. */
+-      if ((cap & CAP_SETUID) &&
+-          lxc_file_cap_is_set(path, CAP_SETUID, CAP_EFFECTIVE) &&
+-          lxc_file_cap_is_set(path, CAP_SETUID, CAP_PERMITTED))
+-              return log_debug(1, "The binary \"%s\" has CAP_SETUID in its CAP_EFFECTIVE and CAP_PERMITTED sets", path);
+-
+-      /* Check if it has the CAP_SETGID capability. */
+-      if ((cap & CAP_SETGID) &&
+-          lxc_file_cap_is_set(path, CAP_SETGID, CAP_EFFECTIVE) &&
+-          lxc_file_cap_is_set(path, CAP_SETGID, CAP_PERMITTED))
+-              return log_debug(1, "The binary \"%s\" has CAP_SETGID in its CAP_EFFECTIVE and CAP_PERMITTED sets", path);
+-
+-      return 0;
+-#else
+-      /*
+-       * If we cannot check for file capabilities we need to give the benefit
+-       * of the doubt. Otherwise we might fail even though all the necessary
+-       * file capabilities are set.
+-       */
+-      DEBUG("Cannot check for file capabilities as full capability support is missing. Manual intervention needed");
+-      return 1;
+-#endif
+-}
+-
+-static int lxc_map_ids_exec_wrapper(void *args)
+-{
+-      execl("/bin/sh", "sh", "-c", (char *)args, (char *)NULL);
+-      return -1;
+-}
+-
+-static struct id_map *find_mapped_hostid_entry(const struct list_head *idmap,
+-                                             unsigned id, enum idtype idtype);
+-
+-int lxc_map_ids(struct list_head *idmap, pid_t pid)
+-{
+-      int fill, left;
+-      uid_t hostuid;
+-      gid_t hostgid;
+-      char u_or_g;
+-      char *pos;
+-      char cmd_output[PATH_MAX];
+-      struct id_map *map;
+-      enum idtype type;
+-      int ret = 0, gidmap = 0, uidmap = 0;
+-      char mapbuf[STRLITERALLEN("new@idmap") + STRLITERALLEN(" ") +
+-                  INTTYPE_TO_STRLEN(pid_t) + STRLITERALLEN(" ") +
+-                  LXC_IDMAPLEN] = {0};
+-      bool had_entry = false, maps_host_root = false, use_shadow = false;
+-
+-      hostuid = geteuid();
+-      hostgid = getegid();
+-
+-      /*
+-       * Check whether caller wants to map host root.
+-       * Due to a security fix newer kernels require CAP_SETFCAP when mapping
+-       * host root into the child userns as you would be able to write fscaps
+-       * that would be valid in the ancestor userns. Mapping host root should
+-       * rarely be the case but LXC is being clever in a bunch of cases.
+-       */
+-      if (find_mapped_hostid_entry(idmap, 0, ID_TYPE_UID))
+-              maps_host_root = true;
+-
+-      /* If new{g,u}idmap exists, that is, if shadow is handing out subuid
+-       * ranges, then insist that root also reserve ranges in subuid. This
+-       * will protected it by preventing another user from being handed the
+-       * range by shadow.
+-       */
+-      uidmap = idmaptool_on_path_and_privileged("newuidmap", CAP_SETUID);
+-      if (uidmap == -ENOENT)
+-              WARN("newuidmap binary is missing");
+-      else if (!uidmap)
+-              WARN("newuidmap is lacking necessary privileges");
+-
+-      gidmap = idmaptool_on_path_and_privileged("newgidmap", CAP_SETGID);
+-      if (gidmap == -ENOENT)
+-              WARN("newgidmap binary is missing");
+-      else if (!gidmap)
+-              WARN("newgidmap is lacking necessary privileges");
+-
+-      if (maps_host_root) {
+-              INFO("Caller maps host root. Writing mapping directly");
+-      } else if (uidmap > 0 && gidmap > 0) {
+-              DEBUG("Functional newuidmap and newgidmap binary found");
+-              use_shadow = true;
+-      } else {
+-              /* In case unprivileged users run application containers via
+-               * execute() or a start*() there are valid cases where they may
+-               * only want to map their own {g,u}id. Let's not block them from
+-               * doing so by requiring geteuid() == 0.
+-               */
+-              DEBUG("No newuidmap and newgidmap binary found. Trying to "
+-                    "write directly with euid %d", hostuid);
+-      }
+-
+-      /* Check if we really need to use newuidmap and newgidmap.
+-      * If the user is only remapping their own {g,u}id, we don't need it.
+-      */
+-      if (use_shadow && list_len(map, idmap, head) == 2) {
+-              use_shadow = false;
+-              list_for_each_entry(map, idmap, head) {
+-                      if (map->idtype == ID_TYPE_UID && map->range == 1 &&
+-                          map->nsid == hostuid && map->hostid == hostuid)
+-                              continue;
+-                      if (map->idtype == ID_TYPE_GID && map->range == 1 &&
+-                          map->nsid == hostgid && map->hostid == hostgid)
+-                              continue;
+-                      use_shadow = true;
+-                      break;
+-              }
+-      }
+-
+-      for (type = ID_TYPE_UID, u_or_g = 'u'; type <= ID_TYPE_GID;
+-           type++, u_or_g = 'g') {
+-              pos = mapbuf;
+-
+-              if (use_shadow)
+-                      pos += sprintf(mapbuf, "new%cidmap %d", u_or_g, pid);
+-
+-              list_for_each_entry(map, idmap, head) {
+-                      if (map->idtype != type)
+-                              continue;
+-
+-                      had_entry = true;
+-
+-                      left = LXC_IDMAPLEN - (pos - mapbuf);
+-                      fill = strnprintf(pos, left, "%s%lu %lu %lu%s",
+-                                      use_shadow ? " " : "", map->nsid,
+-                                      map->hostid, map->range,
+-                                      use_shadow ? "" : "\n");
+-                      /*
+-                       * The kernel only takes <= 4k for writes to
+-                       * /proc/<pid>/{g,u}id_map
+-                       */
+-                      if (fill <= 0)
+-                              return log_error_errno(-1, errno, "Too many %cid mappings defined", u_or_g);
+-
+-                      pos += fill;
+-              }
+-              if (!had_entry)
+-                      continue;
+-
+-              /* Try to catch the output of new{g,u}idmap to make debugging
+-               * easier.
+-               */
+-              if (use_shadow) {
+-                      ret = run_command(cmd_output, sizeof(cmd_output),
+-                                        lxc_map_ids_exec_wrapper,
+-                                        (void *)mapbuf);
+-                      if (ret < 0)
+-                              return log_error(-1, "new%cidmap failed to write mapping \"%s\": %s", u_or_g, cmd_output, mapbuf);
+-                      TRACE("new%cidmap wrote mapping \"%s\"", u_or_g, mapbuf);
+-              } else {
+-                      ret = write_id_mapping(type, pid, mapbuf, pos - mapbuf);
+-                      if (ret < 0)
+-                              return log_error(-1, "Failed to write mapping: %s", mapbuf);
+-                      TRACE("Wrote mapping \"%s\"", mapbuf);
+-              }
+-
+-              memset(mapbuf, 0, sizeof(mapbuf));
+-      }
+-
+-      return 0;
+-}
+-
+ /*
+  * Return the host uid/gid to which the container root is mapped in val.
+  * Return true if id was found, false otherwise.
+@@ -3741,40 +3476,6 @@ static id_t get_mapped_rootid(const stru
+       return LXC_INVALID_GID;
+ }
+-int mapped_hostid(unsigned id, const struct lxc_conf *conf, enum idtype idtype)
+-{
+-      struct id_map *map;
+-
+-      list_for_each_entry(map, &conf->id_map, head) {
+-              if (map->idtype != idtype)
+-                      continue;
+-
+-              if (id >= map->hostid && id < map->hostid + map->range)
+-                      return (id - map->hostid) + map->nsid;
+-      }
+-
+-      return -1;
+-}
+-
+-int find_unmapped_nsid(const struct lxc_conf *conf, enum idtype idtype)
+-{
+-      struct id_map *map;
+-      unsigned int freeid = 0;
+-
+-again:
+-      list_for_each_entry(map, &conf->id_map, head) {
+-              if (map->idtype != idtype)
+-                      continue;
+-
+-              if (freeid >= map->nsid && freeid < map->nsid + map->range) {
+-                      freeid = map->nsid + map->range;
+-                      goto again;
+-              }
+-      }
+-
+-      return freeid;
+-}
+-
+ /*
+  * Mount a proc under @rootfs if proc self points to a pid other than
+  * my own.  This is needed to have a known-good proc mount for setting
+@@ -4910,76 +4611,6 @@ static int run_userns_fn(void *data)
+       return d->fn(d->arg);
+ }
+-static struct id_map *mapped_nsid_add(const struct lxc_conf *conf, unsigned id,
+-                                    enum idtype idtype)
+-{
+-      const struct id_map *map;
+-      struct id_map *retmap;
+-
+-      map = find_mapped_nsid_entry(conf, id, idtype);
+-      if (!map)
+-              return NULL;
+-
+-      retmap = zalloc(sizeof(*retmap));
+-      if (!retmap)
+-              return NULL;
+-
+-      memcpy(retmap, map, sizeof(*retmap));
+-      return retmap;
+-}
+-
+-static struct id_map *find_mapped_hostid_entry(const struct list_head *idmap,
+-                                             unsigned id, enum idtype idtype)
+-{
+-      struct id_map *retmap = NULL;
+-      struct id_map *map;
+-
+-      list_for_each_entry(map, idmap, head) {
+-              if (map->idtype != idtype)
+-                      continue;
+-
+-              if (id >= map->hostid && id < map->hostid + map->range) {
+-                      retmap = map;
+-                      break;
+-              }
+-      }
+-
+-      return retmap;
+-}
+-
+-/* Allocate a new {g,u}id mapping for the given {g,u}id. Re-use an already
+- * existing one or establish a new one.
+- */
+-static struct id_map *mapped_hostid_add(const struct lxc_conf *conf, uid_t id,
+-                                      enum idtype type)
+-{
+-      __do_free struct id_map *entry = NULL;
+-      int hostid_mapped;
+-      struct id_map *tmp = NULL;
+-
+-      entry = zalloc(sizeof(*entry));
+-      if (!entry)
+-              return NULL;
+-
+-      /* Reuse existing mapping. */
+-      tmp = find_mapped_hostid_entry(&conf->id_map, id, type);
+-      if (tmp) {
+-              memcpy(entry, tmp, sizeof(*entry));
+-      } else {
+-              /* Find new mapping. */
+-              hostid_mapped = find_unmapped_nsid(conf, type);
+-              if (hostid_mapped < 0)
+-                      return log_debug(NULL, "Failed to find free mapping for id %d", id);
+-
+-              entry->idtype = type;
+-              entry->nsid = hostid_mapped;
+-              entry->hostid = (unsigned long)id;
+-              entry->range = 1;
+-      }
+-
+-      return move_ptr(entry);
+-}
+-
+ static int get_minimal_idmap(const struct lxc_conf *conf, uid_t *resuid,
+                            gid_t *resgid, struct list_head *head_ret)
+ {
+--- a/src/lxc/conf.h
++++ b/src/lxc/conf.h
+@@ -578,9 +578,6 @@ struct lxc_conf {
+       __u64 sched_core_cookie;
+ };
+-__hidden extern int write_id_mapping(enum idtype idtype, pid_t pid, const char *buf, size_t buf_size)
+-    __access_r(3, 4);
+-
+ extern thread_local struct lxc_conf *current_config;
+ __hidden extern int run_lxc_hooks(const char *name, char *hook, struct lxc_conf *conf, char *argv[]);
+@@ -592,7 +589,6 @@ __hidden extern void lxc_storage_put(str
+ __hidden extern int lxc_rootfs_init(struct lxc_conf *conf, bool userns);
+ __hidden extern int lxc_rootfs_prepare_parent(struct lxc_handler *handler);
+ __hidden extern int lxc_idmapped_mounts_parent(struct lxc_handler *handler);
+-__hidden extern int lxc_map_ids(struct list_head *idmap, pid_t pid);
+ __hidden extern int lxc_create_tty(const char *name, struct lxc_conf *conf);
+ __hidden extern void lxc_delete_tty(struct lxc_tty_info *ttys);
+ __hidden extern int lxc_clear_config_caps(struct lxc_conf *c);
+@@ -611,8 +607,6 @@ __hidden extern int lxc_setup_rootfs_pre
+ __hidden extern int lxc_setup(struct lxc_handler *handler);
+ __hidden extern int lxc_setup_parent(struct lxc_handler *handler);
+ __hidden extern int setup_resource_limits(struct lxc_conf *conf, pid_t pid);
+-__hidden extern int find_unmapped_nsid(const struct lxc_conf *conf, enum idtype idtype);
+-__hidden extern int mapped_hostid(unsigned id, const struct lxc_conf *conf, enum idtype idtype);
+ __hidden extern int userns_exec_1(const struct lxc_conf *conf, int (*fn)(void *), void *data,
+                                 const char *fn_name);
+ __hidden extern int userns_exec_full(struct lxc_conf *conf, int (*fn)(void *), void *data,
+--- /dev/null
++++ b/src/lxc/idmap_utils.c
+@@ -0,0 +1,391 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++
++#include "config.h"
++
++#include <errno.h>
++#include <fcntl.h>
++#include <malloc.h>
++#include <pthread.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <sys/file.h>
++#include <unistd.h>
++
++#include "lxc.h"
++
++#include "log.h"
++#include "lxclock.h"
++#include "memory_utils.h"
++#include "utils.h"
++
++lxc_log_define(idmap_utils, lxc);
++
++int write_id_mapping(enum idtype idtype, pid_t pid, const char *buf,
++                   size_t buf_size)
++{
++      __do_close int fd = -EBADF;
++      int ret;
++      char path[PATH_MAX];
++
++      if (geteuid() != 0 && idtype == ID_TYPE_GID) {
++              __do_close int setgroups_fd = -EBADF;
++
++              ret = strnprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
++              if (ret < 0)
++                      return -E2BIG;
++
++              setgroups_fd = open(path, O_WRONLY);
++              if (setgroups_fd < 0 && errno != ENOENT)
++                      return log_error_errno(-1, errno, "Failed to open \"%s\"", path);
++
++              if (setgroups_fd >= 0) {
++                      ret = lxc_write_nointr(setgroups_fd, "deny\n",
++                                             STRLITERALLEN("deny\n"));
++                      if (ret != STRLITERALLEN("deny\n"))
++                              return log_error_errno(-1, errno, "Failed to write \"deny\" to \"/proc/%d/setgroups\"", pid);
++                      TRACE("Wrote \"deny\" to \"/proc/%d/setgroups\"", pid);
++              }
++      }
++
++      ret = strnprintf(path, sizeof(path), "/proc/%d/%cid_map", pid,
++                     idtype == ID_TYPE_UID ? 'u' : 'g');
++      if (ret < 0)
++              return -E2BIG;
++
++      fd = open(path, O_WRONLY | O_CLOEXEC);
++      if (fd < 0)
++              return log_error_errno(-1, errno, "Failed to open \"%s\"", path);
++
++      ret = lxc_write_nointr(fd, buf, buf_size);
++      if (ret < 0 || (size_t)ret != buf_size)
++              return log_error_errno(-1, errno, "Failed to write %cid mapping to \"%s\"",
++                                     idtype == ID_TYPE_UID ? 'u' : 'g', path);
++
++      return 0;
++}
++
++/* Check whether a binary exist and has either CAP_SETUID, CAP_SETGID or both.
++ *
++ * @return  1      if functional binary was found
++ * @return  0      if binary exists but is lacking privilege
++ * @return -ENOENT if binary does not exist
++ * @return -EINVAL if cap to check is neither CAP_SETUID nor CAP_SETGID
++ */
++static int idmaptool_on_path_and_privileged(const char *binary, cap_value_t cap)
++{
++      __do_free char *path = NULL;
++      int ret;
++      struct stat st;
++
++      if (cap != CAP_SETUID && cap != CAP_SETGID)
++              return ret_errno(EINVAL);
++
++      path = on_path(binary, NULL);
++      if (!path)
++              return ret_errno(ENOENT);
++
++      ret = stat(path, &st);
++      if (ret < 0)
++              return -errno;
++
++      /* Check if the binary is setuid. */
++      if (st.st_mode & S_ISUID)
++              return log_debug(1, "The binary \"%s\" does have the setuid bit set", path);
++
++#if HAVE_LIBCAP && LIBCAP_SUPPORTS_FILE_CAPABILITIES
++      /* Check if it has the CAP_SETUID capability. */
++      if ((cap & CAP_SETUID) &&
++          lxc_file_cap_is_set(path, CAP_SETUID, CAP_EFFECTIVE) &&
++          lxc_file_cap_is_set(path, CAP_SETUID, CAP_PERMITTED))
++              return log_debug(1, "The binary \"%s\" has CAP_SETUID in its CAP_EFFECTIVE and CAP_PERMITTED sets", path);
++
++      /* Check if it has the CAP_SETGID capability. */
++      if ((cap & CAP_SETGID) &&
++          lxc_file_cap_is_set(path, CAP_SETGID, CAP_EFFECTIVE) &&
++          lxc_file_cap_is_set(path, CAP_SETGID, CAP_PERMITTED))
++              return log_debug(1, "The binary \"%s\" has CAP_SETGID in its CAP_EFFECTIVE and CAP_PERMITTED sets", path);
++
++      return 0;
++#else
++      /*
++       * If we cannot check for file capabilities we need to give the benefit
++       * of the doubt. Otherwise we might fail even though all the necessary
++       * file capabilities are set.
++       */
++      DEBUG("Cannot check for file capabilities as full capability support is missing. Manual intervention needed");
++      return 1;
++#endif
++}
++
++static int lxc_map_ids_exec_wrapper(void *args)
++{
++      execl("/bin/sh", "sh", "-c", (char *)args, (char *)NULL);
++      return -1;
++}
++
++static struct id_map *find_mapped_hostid_entry(const struct list_head *idmap,
++                                             unsigned id, enum idtype idtype);
++
++int lxc_map_ids(struct list_head *idmap, pid_t pid)
++{
++      int fill, left;
++      uid_t hostuid;
++      gid_t hostgid;
++      char u_or_g;
++      char *pos;
++      char cmd_output[PATH_MAX];
++      struct id_map *map;
++      enum idtype type;
++      int ret = 0, gidmap = 0, uidmap = 0;
++      char mapbuf[STRLITERALLEN("new@idmap") + STRLITERALLEN(" ") +
++                  INTTYPE_TO_STRLEN(pid_t) + STRLITERALLEN(" ") +
++                  LXC_IDMAPLEN] = {0};
++      bool had_entry = false, maps_host_root = false, use_shadow = false;
++
++      hostuid = geteuid();
++      hostgid = getegid();
++
++      /*
++       * Check whether caller wants to map host root.
++       * Due to a security fix newer kernels require CAP_SETFCAP when mapping
++       * host root into the child userns as you would be able to write fscaps
++       * that would be valid in the ancestor userns. Mapping host root should
++       * rarely be the case but LXC is being clever in a bunch of cases.
++       */
++      if (find_mapped_hostid_entry(idmap, 0, ID_TYPE_UID))
++              maps_host_root = true;
++
++      /* If new{g,u}idmap exists, that is, if shadow is handing out subuid
++       * ranges, then insist that root also reserve ranges in subuid. This
++       * will protected it by preventing another user from being handed the
++       * range by shadow.
++       */
++      uidmap = idmaptool_on_path_and_privileged("newuidmap", CAP_SETUID);
++      if (uidmap == -ENOENT)
++              WARN("newuidmap binary is missing");
++      else if (!uidmap)
++              WARN("newuidmap is lacking necessary privileges");
++
++      gidmap = idmaptool_on_path_and_privileged("newgidmap", CAP_SETGID);
++      if (gidmap == -ENOENT)
++              WARN("newgidmap binary is missing");
++      else if (!gidmap)
++              WARN("newgidmap is lacking necessary privileges");
++
++      if (maps_host_root) {
++              INFO("Caller maps host root. Writing mapping directly");
++      } else if (uidmap > 0 && gidmap > 0) {
++              DEBUG("Functional newuidmap and newgidmap binary found");
++              use_shadow = true;
++      } else {
++              /* In case unprivileged users run application containers via
++               * execute() or a start*() there are valid cases where they may
++               * only want to map their own {g,u}id. Let's not block them from
++               * doing so by requiring geteuid() == 0.
++               */
++              DEBUG("No newuidmap and newgidmap binary found. Trying to "
++                    "write directly with euid %d", hostuid);
++      }
++
++      /* Check if we really need to use newuidmap and newgidmap.
++      * If the user is only remapping their own {g,u}id, we don't need it.
++      */
++      if (use_shadow && list_len(map, idmap, head) == 2) {
++              use_shadow = false;
++              list_for_each_entry(map, idmap, head) {
++                      if (map->idtype == ID_TYPE_UID && map->range == 1 &&
++                          map->nsid == hostuid && map->hostid == hostuid)
++                              continue;
++                      if (map->idtype == ID_TYPE_GID && map->range == 1 &&
++                          map->nsid == hostgid && map->hostid == hostgid)
++                              continue;
++                      use_shadow = true;
++                      break;
++              }
++      }
++
++      for (type = ID_TYPE_UID, u_or_g = 'u'; type <= ID_TYPE_GID;
++           type++, u_or_g = 'g') {
++              pos = mapbuf;
++
++              if (use_shadow)
++                      pos += sprintf(mapbuf, "new%cidmap %d", u_or_g, pid);
++
++              list_for_each_entry(map, idmap, head) {
++                      if (map->idtype != type)
++                              continue;
++
++                      had_entry = true;
++
++                      left = LXC_IDMAPLEN - (pos - mapbuf);
++                      fill = strnprintf(pos, left, "%s%lu %lu %lu%s",
++                                      use_shadow ? " " : "", map->nsid,
++                                      map->hostid, map->range,
++                                      use_shadow ? "" : "\n");
++                      /*
++                       * The kernel only takes <= 4k for writes to
++                       * /proc/<pid>/{g,u}id_map
++                       */
++                      if (fill <= 0)
++                              return log_error_errno(-1, errno, "Too many %cid mappings defined", u_or_g);
++
++                      pos += fill;
++              }
++              if (!had_entry)
++                      continue;
++
++              /* Try to catch the output of new{g,u}idmap to make debugging
++               * easier.
++               */
++              if (use_shadow) {
++                      ret = run_command(cmd_output, sizeof(cmd_output),
++                                        lxc_map_ids_exec_wrapper,
++                                        (void *)mapbuf);
++                      if (ret < 0)
++                              return log_error(-1, "new%cidmap failed to write mapping \"%s\": %s", u_or_g, cmd_output, mapbuf);
++                      TRACE("new%cidmap wrote mapping \"%s\"", u_or_g, mapbuf);
++              } else {
++                      ret = write_id_mapping(type, pid, mapbuf, pos - mapbuf);
++                      if (ret < 0)
++                              return log_error(-1, "Failed to write mapping: %s", mapbuf);
++                      TRACE("Wrote mapping \"%s\"", mapbuf);
++              }
++
++              memset(mapbuf, 0, sizeof(mapbuf));
++      }
++
++      return 0;
++}
++
++int mapped_hostid(unsigned id, const struct lxc_conf *conf, enum idtype idtype)
++{
++      struct id_map *map;
++
++      list_for_each_entry(map, &conf->id_map, head) {
++              if (map->idtype != idtype)
++                      continue;
++
++              if (id >= map->hostid && id < map->hostid + map->range)
++                      return (id - map->hostid) + map->nsid;
++      }
++
++      return -1;
++}
++
++int find_unmapped_nsid(const struct lxc_conf *conf, enum idtype idtype)
++{
++      struct id_map *map;
++      unsigned int freeid = 0;
++
++again:
++      list_for_each_entry(map, &conf->id_map, head) {
++              if (map->idtype != idtype)
++                      continue;
++
++              if (freeid >= map->nsid && freeid < map->nsid + map->range) {
++                      freeid = map->nsid + map->range;
++                      goto again;
++              }
++      }
++
++      return freeid;
++}
++
++static const struct id_map *find_mapped_nsid_entry(const struct lxc_conf *conf,
++                                                 unsigned id,
++                                                 enum idtype idtype)
++{
++      struct id_map *map;
++      struct id_map *retmap = NULL;
++
++      /* Shortcut for container's root mappings. */
++      if (id == 0) {
++              if (idtype == ID_TYPE_UID)
++                      return conf->root_nsuid_map;
++
++              if (idtype == ID_TYPE_GID)
++                      return conf->root_nsgid_map;
++      }
++
++      list_for_each_entry(map, &conf->id_map, head) {
++              if (map->idtype != idtype)
++                      continue;
++
++              if (id >= map->nsid && id < map->nsid + map->range) {
++                      retmap = map;
++                      break;
++              }
++      }
++
++      return retmap;
++}
++
++struct id_map *mapped_nsid_add(const struct lxc_conf *conf, unsigned id,
++                                    enum idtype idtype)
++{
++      const struct id_map *map;
++      struct id_map *retmap;
++
++      map = find_mapped_nsid_entry(conf, id, idtype);
++      if (!map)
++              return NULL;
++
++      retmap = zalloc(sizeof(*retmap));
++      if (!retmap)
++              return NULL;
++
++      memcpy(retmap, map, sizeof(*retmap));
++      return retmap;
++}
++
++static struct id_map *find_mapped_hostid_entry(const struct list_head *idmap,
++                                             unsigned id, enum idtype idtype)
++{
++      struct id_map *retmap = NULL;
++      struct id_map *map;
++
++      list_for_each_entry(map, idmap, head) {
++              if (map->idtype != idtype)
++                      continue;
++
++              if (id >= map->hostid && id < map->hostid + map->range) {
++                      retmap = map;
++                      break;
++              }
++      }
++
++      return retmap;
++}
++
++/* Allocate a new {g,u}id mapping for the given {g,u}id. Re-use an already
++ * existing one or establish a new one.
++ */
++struct id_map *mapped_hostid_add(const struct lxc_conf *conf, uid_t id,
++                                      enum idtype type)
++{
++      __do_free struct id_map *entry = NULL;
++      int hostid_mapped;
++      struct id_map *tmp = NULL;
++
++      entry = zalloc(sizeof(*entry));
++      if (!entry)
++              return NULL;
++
++      /* Reuse existing mapping. */
++      tmp = find_mapped_hostid_entry(&conf->id_map, id, type);
++      if (tmp) {
++              memcpy(entry, tmp, sizeof(*entry));
++      } else {
++              /* Find new mapping. */
++              hostid_mapped = find_unmapped_nsid(conf, type);
++              if (hostid_mapped < 0)
++                      return log_debug(NULL, "Failed to find free mapping for id %d", id);
++
++              entry->idtype = type;
++              entry->nsid = hostid_mapped;
++              entry->hostid = (unsigned long)id;
++              entry->range = 1;
++      }
++
++      return move_ptr(entry);
++}
+--- /dev/null
++++ b/src/lxc/idmap_utils.h
+@@ -0,0 +1,30 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++
++#ifndef __LXC_IDMAP_UTILS_H
++#define __LXC_IDMAP_UTILS_H
++
++#include "config.h"
++
++#include <fcntl.h>
++#include <semaphore.h>
++#include <string.h>
++#include <sys/file.h>
++#include <sys/stat.h>
++#include <time.h>
++#include <unistd.h>
++
++#include "compiler.h"
++#include "conf.h"
++
++__hidden extern int lxc_map_ids(struct list_head *idmap, pid_t pid);
++__hidden extern int find_unmapped_nsid(const struct lxc_conf *conf, enum idtype idtype);
++__hidden extern int mapped_hostid(unsigned id, const struct lxc_conf *conf, enum idtype idtype);
++__hidden extern struct id_map *mapped_hostid_add(const struct lxc_conf *conf, uid_t id,
++                                               enum idtype type);
++__hidden extern struct id_map *mapped_nsid_add(const struct lxc_conf *conf, unsigned id,
++                                             enum idtype idtype);
++
++__hidden extern int write_id_mapping(enum idtype idtype, pid_t pid, const char *buf, size_t buf_size)
++    __access_r(3, 4);
++
++#endif
+--- a/src/lxc/lxccontainer.c
++++ b/src/lxc/lxccontainer.c
+@@ -39,6 +39,7 @@
+ #include "confile_utils.h"
+ #include "criu.h"
+ #include "error.h"
++#include "idmap_utils.h"
+ #include "initutils.h"
+ #include "log.h"
+ #include "lxc.h"
+--- a/src/lxc/meson.build
++++ b/src/lxc/meson.build
+@@ -86,6 +86,8 @@ liblxc_sources = files(
+     'file_utils.h',
+     'freezer.c',
+     'hlist.h',
++    'idmap_utils.c',
++    'idmap_utils.h',
+     'initutils.c',
+     'initutils.h',
+     'list.h',
+--- a/src/lxc/start.c
++++ b/src/lxc/start.c
+@@ -38,6 +38,7 @@
+ #include "confile_utils.h"
+ #include "error.h"
+ #include "file_utils.h"
++#include "idmap_utils.h"
+ #include "list.h"
+ #include "log.h"
+ #include "lsm/lsm.h"
+--- a/src/lxc/tools/lxc_unshare.c
++++ b/src/lxc/tools/lxc_unshare.c
+@@ -21,6 +21,7 @@
+ #include "arguments.h"
+ #include "caps.h"
++#include "idmap_utils.h"
+ #include "list.h"
+ #include "log.h"
+ #include "namespace.h"
diff --git a/utils/lxc/patches/005-conf-reorganize-split-code-to-utils.c.patch b/utils/lxc/patches/005-conf-reorganize-split-code-to-utils.c.patch
new file mode 100644 (file)
index 0000000..55cf89b
--- /dev/null
@@ -0,0 +1,469 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sun, 18 Feb 2024 15:24:29 +0100
+Subject: [PATCH] conf: reorganize/split code to utils.c
+
+Move run_script/run_script_argv helpers to utils.c
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit 9bb31888168eaa2ceb1302439aa638c7850a6841)
+---
+ src/lxc/conf.c  | 201 ------------------------------------------------
+ src/lxc/conf.h  |   4 +-
+ src/lxc/utils.c | 201 ++++++++++++++++++++++++++++++++++++++++++++++++
+ src/lxc/utils.h |   4 +
+ 4 files changed, 206 insertions(+), 204 deletions(-)
+
+--- a/src/lxc/conf.c
++++ b/src/lxc/conf.c
+@@ -285,207 +285,6 @@ static struct limit_opt limit_opt[] = {
+ #endif
+ };
+-static int run_buffer(char *buffer)
+-{
+-      __do_free char *output = NULL;
+-      __do_lxc_pclose struct lxc_popen_FILE *f = NULL;
+-      int fd, ret;
+-
+-      f = lxc_popen(buffer);
+-      if (!f)
+-              return log_error_errno(-1, errno, "Failed to popen() %s", buffer);
+-
+-      output = zalloc(LXC_LOG_BUFFER_SIZE);
+-      if (!output)
+-              return log_error_errno(-1, ENOMEM, "Failed to allocate memory for %s", buffer);
+-
+-      fd = fileno(f->f);
+-      if (fd < 0)
+-              return log_error_errno(-1, errno, "Failed to retrieve underlying file descriptor");
+-
+-      for (int i = 0; i < 10; i++) {
+-              ssize_t bytes_read;
+-
+-              bytes_read = lxc_read_nointr(fd, output, LXC_LOG_BUFFER_SIZE - 1);
+-              if (bytes_read > 0) {
+-                      output[bytes_read] = '\0';
+-                      DEBUG("Script %s produced output: %s", buffer, output);
+-                      continue;
+-              }
+-
+-              break;
+-      }
+-
+-      ret = lxc_pclose(move_ptr(f));
+-      if (ret == -1)
+-              return log_error_errno(-1, errno, "Script exited with error");
+-      else if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0)
+-              return log_error(-1, "Script exited with status %d", WEXITSTATUS(ret));
+-      else if (WIFSIGNALED(ret))
+-              return log_error(-1, "Script terminated by signal %d", WTERMSIG(ret));
+-
+-      return 0;
+-}
+-
+-int run_script_argv(const char *name, unsigned int hook_version,
+-                  const char *section, const char *script,
+-                  const char *hookname, char **argv)
+-{
+-      __do_free char *buffer = NULL;
+-      int buf_pos, i, ret;
+-      size_t size = 0;
+-
+-      if (hook_version == 0)
+-              INFO("Executing script \"%s\" for container \"%s\", config section \"%s\"",
+-                   script, name, section);
+-      else
+-              INFO("Executing script \"%s\" for container \"%s\"", script, name);
+-
+-      for (i = 0; argv && argv[i]; i++)
+-              size += strlen(argv[i]) + 1;
+-
+-      size += STRLITERALLEN("exec");
+-      size++;
+-      size += strlen(script);
+-      size++;
+-
+-      if (size > INT_MAX)
+-              return -EFBIG;
+-
+-      if (hook_version == 0) {
+-              size += strlen(hookname);
+-              size++;
+-
+-              size += strlen(name);
+-              size++;
+-
+-              size += strlen(section);
+-              size++;
+-
+-              if (size > INT_MAX)
+-                      return -EFBIG;
+-      }
+-
+-      buffer = zalloc(size);
+-      if (!buffer)
+-              return -ENOMEM;
+-
+-      if (hook_version == 0)
+-              buf_pos = strnprintf(buffer, size, "exec %s %s %s %s", script, name, section, hookname);
+-      else
+-              buf_pos = strnprintf(buffer, size, "exec %s", script);
+-      if (buf_pos < 0)
+-              return log_error_errno(-1, errno, "Failed to create command line for script \"%s\"", script);
+-
+-      if (hook_version == 1) {
+-              ret = setenv("LXC_HOOK_TYPE", hookname, 1);
+-              if (ret < 0) {
+-                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_HOOK_TYPE=%s", hookname);
+-              }
+-              TRACE("Set environment variable: LXC_HOOK_TYPE=%s", hookname);
+-
+-              ret = setenv("LXC_HOOK_SECTION", section, 1);
+-              if (ret < 0)
+-                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_HOOK_SECTION=%s", section);
+-              TRACE("Set environment variable: LXC_HOOK_SECTION=%s", section);
+-
+-              if (strequal(section, "net")) {
+-                      char *parent;
+-
+-                      if (!argv || !argv[0])
+-                              return -1;
+-
+-                      ret = setenv("LXC_NET_TYPE", argv[0], 1);
+-                      if (ret < 0)
+-                              return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_TYPE=%s", argv[0]);
+-                      TRACE("Set environment variable: LXC_NET_TYPE=%s", argv[0]);
+-
+-                      parent = argv[1] ? argv[1] : "";
+-
+-                      if (strequal(argv[0], "macvlan")) {
+-                              ret = setenv("LXC_NET_PARENT", parent, 1);
+-                              if (ret < 0)
+-                                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_PARENT=%s", parent);
+-                              TRACE("Set environment variable: LXC_NET_PARENT=%s", parent);
+-                      } else if (strequal(argv[0], "phys")) {
+-                              ret = setenv("LXC_NET_PARENT", parent, 1);
+-                              if (ret < 0)
+-                                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_PARENT=%s", parent);
+-                              TRACE("Set environment variable: LXC_NET_PARENT=%s", parent);
+-                      } else if (strequal(argv[0], "veth")) {
+-                              char *peer = argv[2] ? argv[2] : "";
+-
+-                              ret = setenv("LXC_NET_PEER", peer, 1);
+-                              if (ret < 0)
+-                                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_PEER=%s", peer);
+-                              TRACE("Set environment variable: LXC_NET_PEER=%s", peer);
+-
+-                              ret = setenv("LXC_NET_PARENT", parent, 1);
+-                              if (ret < 0)
+-                                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_PARENT=%s", parent);
+-                              TRACE("Set environment variable: LXC_NET_PARENT=%s", parent);
+-                      }
+-              }
+-      }
+-
+-      for (i = 0; argv && argv[i]; i++) {
+-              size_t len = size - buf_pos;
+-
+-              ret = strnprintf(buffer + buf_pos, len, " %s", argv[i]);
+-              if (ret < 0)
+-                      return log_error_errno(-1, errno, "Failed to create command line for script \"%s\"", script);
+-              buf_pos += ret;
+-      }
+-
+-      return run_buffer(buffer);
+-}
+-
+-int run_script(const char *name, const char *section, const char *script, ...)
+-{
+-      __do_free char *buffer = NULL;
+-      int ret;
+-      char *p;
+-      va_list ap;
+-      size_t size = 0;
+-
+-      INFO("Executing script \"%s\" for container \"%s\", config section \"%s\"",
+-           script, name, section);
+-
+-      va_start(ap, script);
+-      while ((p = va_arg(ap, char *)))
+-              size += strlen(p) + 1;
+-      va_end(ap);
+-
+-      size += STRLITERALLEN("exec");
+-      size += strlen(script);
+-      size += strlen(name);
+-      size += strlen(section);
+-      size += 4;
+-
+-      if (size > INT_MAX)
+-              return -1;
+-
+-      buffer = must_realloc(NULL, size);
+-      ret = strnprintf(buffer, size, "exec %s %s %s", script, name, section);
+-      if (ret < 0)
+-              return -1;
+-
+-      va_start(ap, script);
+-      while ((p = va_arg(ap, char *))) {
+-              int len = size - ret;
+-              int rc;
+-              rc = strnprintf(buffer + ret, len, " %s", p);
+-              if (rc < 0) {
+-                      va_end(ap);
+-                      return -1;
+-              }
+-              ret += rc;
+-      }
+-      va_end(ap);
+-
+-      return run_buffer(buffer);
+-}
+-
+ int lxc_storage_prepare(struct lxc_conf *conf)
+ {
+       int ret;
+--- a/src/lxc/conf.h
++++ b/src/lxc/conf.h
+@@ -30,6 +30,7 @@
+ #include "string_utils.h"
+ #include "syscall_wrappers.h"
+ #include "terminal.h"
++#include "utils.h"
+ #if HAVE_SYS_RESOURCE_H
+ #include <sys/resource.h>
+@@ -619,9 +620,6 @@ __hidden extern void tmp_proc_unmount(st
+ __hidden extern void suggest_default_idmap(void);
+ __hidden extern FILE *make_anonymous_mount_file(const struct list_head *mount,
+                                               bool include_nesting_helpers);
+-__hidden extern int run_script(const char *name, const char *section, const char *script, ...);
+-__hidden extern int run_script_argv(const char *name, unsigned int hook_version, const char *section,
+-                                  const char *script, const char *hookname, char **argsin);
+ __hidden extern bool has_cap(__u32 cap, struct lxc_conf *conf);
+ static inline bool lxc_wants_cap(__u32 cap, struct lxc_conf *conf)
+--- a/src/lxc/utils.c
++++ b/src/lxc/utils.c
+@@ -533,6 +533,207 @@ int lxc_pclose(struct lxc_popen_FILE *fp
+       return wstatus;
+ }
++static int run_buffer(char *buffer)
++{
++      __do_free char *output = NULL;
++      __do_lxc_pclose struct lxc_popen_FILE *f = NULL;
++      int fd, ret;
++
++      f = lxc_popen(buffer);
++      if (!f)
++              return log_error_errno(-1, errno, "Failed to popen() %s", buffer);
++
++      output = zalloc(LXC_LOG_BUFFER_SIZE);
++      if (!output)
++              return log_error_errno(-1, ENOMEM, "Failed to allocate memory for %s", buffer);
++
++      fd = fileno(f->f);
++      if (fd < 0)
++              return log_error_errno(-1, errno, "Failed to retrieve underlying file descriptor");
++
++      for (int i = 0; i < 10; i++) {
++              ssize_t bytes_read;
++
++              bytes_read = lxc_read_nointr(fd, output, LXC_LOG_BUFFER_SIZE - 1);
++              if (bytes_read > 0) {
++                      output[bytes_read] = '\0';
++                      DEBUG("Script %s produced output: %s", buffer, output);
++                      continue;
++              }
++
++              break;
++      }
++
++      ret = lxc_pclose(move_ptr(f));
++      if (ret == -1)
++              return log_error_errno(-1, errno, "Script exited with error");
++      else if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0)
++              return log_error(-1, "Script exited with status %d", WEXITSTATUS(ret));
++      else if (WIFSIGNALED(ret))
++              return log_error(-1, "Script terminated by signal %d", WTERMSIG(ret));
++
++      return 0;
++}
++
++int run_script_argv(const char *name, unsigned int hook_version,
++                  const char *section, const char *script,
++                  const char *hookname, char **argv)
++{
++      __do_free char *buffer = NULL;
++      int buf_pos, i, ret;
++      size_t size = 0;
++
++      if (hook_version == 0)
++              INFO("Executing script \"%s\" for container \"%s\", config section \"%s\"",
++                   script, name, section);
++      else
++              INFO("Executing script \"%s\" for container \"%s\"", script, name);
++
++      for (i = 0; argv && argv[i]; i++)
++              size += strlen(argv[i]) + 1;
++
++      size += STRLITERALLEN("exec");
++      size++;
++      size += strlen(script);
++      size++;
++
++      if (size > INT_MAX)
++              return -EFBIG;
++
++      if (hook_version == 0) {
++              size += strlen(hookname);
++              size++;
++
++              size += strlen(name);
++              size++;
++
++              size += strlen(section);
++              size++;
++
++              if (size > INT_MAX)
++                      return -EFBIG;
++      }
++
++      buffer = zalloc(size);
++      if (!buffer)
++              return -ENOMEM;
++
++      if (hook_version == 0)
++              buf_pos = strnprintf(buffer, size, "exec %s %s %s %s", script, name, section, hookname);
++      else
++              buf_pos = strnprintf(buffer, size, "exec %s", script);
++      if (buf_pos < 0)
++              return log_error_errno(-1, errno, "Failed to create command line for script \"%s\"", script);
++
++      if (hook_version == 1) {
++              ret = setenv("LXC_HOOK_TYPE", hookname, 1);
++              if (ret < 0) {
++                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_HOOK_TYPE=%s", hookname);
++              }
++              TRACE("Set environment variable: LXC_HOOK_TYPE=%s", hookname);
++
++              ret = setenv("LXC_HOOK_SECTION", section, 1);
++              if (ret < 0)
++                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_HOOK_SECTION=%s", section);
++              TRACE("Set environment variable: LXC_HOOK_SECTION=%s", section);
++
++              if (strequal(section, "net")) {
++                      char *parent;
++
++                      if (!argv || !argv[0])
++                              return -1;
++
++                      ret = setenv("LXC_NET_TYPE", argv[0], 1);
++                      if (ret < 0)
++                              return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_TYPE=%s", argv[0]);
++                      TRACE("Set environment variable: LXC_NET_TYPE=%s", argv[0]);
++
++                      parent = argv[1] ? argv[1] : "";
++
++                      if (strequal(argv[0], "macvlan")) {
++                              ret = setenv("LXC_NET_PARENT", parent, 1);
++                              if (ret < 0)
++                                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_PARENT=%s", parent);
++                              TRACE("Set environment variable: LXC_NET_PARENT=%s", parent);
++                      } else if (strequal(argv[0], "phys")) {
++                              ret = setenv("LXC_NET_PARENT", parent, 1);
++                              if (ret < 0)
++                                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_PARENT=%s", parent);
++                              TRACE("Set environment variable: LXC_NET_PARENT=%s", parent);
++                      } else if (strequal(argv[0], "veth")) {
++                              char *peer = argv[2] ? argv[2] : "";
++
++                              ret = setenv("LXC_NET_PEER", peer, 1);
++                              if (ret < 0)
++                                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_PEER=%s", peer);
++                              TRACE("Set environment variable: LXC_NET_PEER=%s", peer);
++
++                              ret = setenv("LXC_NET_PARENT", parent, 1);
++                              if (ret < 0)
++                                      return log_error_errno(-1, errno, "Failed to set environment variable: LXC_NET_PARENT=%s", parent);
++                              TRACE("Set environment variable: LXC_NET_PARENT=%s", parent);
++                      }
++              }
++      }
++
++      for (i = 0; argv && argv[i]; i++) {
++              size_t len = size - buf_pos;
++
++              ret = strnprintf(buffer + buf_pos, len, " %s", argv[i]);
++              if (ret < 0)
++                      return log_error_errno(-1, errno, "Failed to create command line for script \"%s\"", script);
++              buf_pos += ret;
++      }
++
++      return run_buffer(buffer);
++}
++
++int run_script(const char *name, const char *section, const char *script, ...)
++{
++      __do_free char *buffer = NULL;
++      int ret;
++      char *p;
++      va_list ap;
++      size_t size = 0;
++
++      INFO("Executing script \"%s\" for container \"%s\", config section \"%s\"",
++           script, name, section);
++
++      va_start(ap, script);
++      while ((p = va_arg(ap, char *)))
++              size += strlen(p) + 1;
++      va_end(ap);
++
++      size += STRLITERALLEN("exec");
++      size += strlen(script);
++      size += strlen(name);
++      size += strlen(section);
++      size += 4;
++
++      if (size > INT_MAX)
++              return -1;
++
++      buffer = must_realloc(NULL, size);
++      ret = strnprintf(buffer, size, "exec %s %s %s", script, name, section);
++      if (ret < 0)
++              return -1;
++
++      va_start(ap, script);
++      while ((p = va_arg(ap, char *))) {
++              int len = size - ret;
++              int rc;
++              rc = strnprintf(buffer + ret, len, " %s", p);
++              if (rc < 0) {
++                      va_end(ap);
++                      return -1;
++              }
++              ret += rc;
++      }
++      va_end(ap);
++
++      return run_buffer(buffer);
++}
++
+ int randseed(bool srand_it)
+ {
+       __do_fclose FILE *f = NULL;
+--- a/src/lxc/utils.h
++++ b/src/lxc/utils.h
+@@ -75,6 +75,10 @@ static inline void __auto_lxc_pclose__(s
+ }
+ #define __do_lxc_pclose __attribute__((__cleanup__(__auto_lxc_pclose__)))
++__hidden extern int run_script(const char *name, const char *section, const char *script, ...);
++__hidden extern int run_script_argv(const char *name, unsigned int hook_version, const char *section,
++                                  const char *script, const char *hookname, char **argsin);
++
+ /*
+  * wait on a child we forked
+  */
diff --git a/utils/lxc/patches/006-confile-unhide-lxc_config_parse_arch-helper.patch b/utils/lxc/patches/006-confile-unhide-lxc_config_parse_arch-helper.patch
new file mode 100644 (file)
index 0000000..bdcd4e7
--- /dev/null
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sun, 18 Feb 2024 15:43:20 +0100
+Subject: [PATCH] confile: unhide lxc_config_parse_arch() helper
+
+Looks safe enough to be available for liblxc users.
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit 42eeffcb05c468fd7b3a90eeda4a3abe9f26844b)
+---
+ src/lxc/confile.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/src/lxc/confile.h
++++ b/src/lxc/confile.h
+@@ -88,7 +88,7 @@ extern void lxc_config_define_free(struc
+  * Parse personality of the container. Returns 0 if personality is valid,
+  * negative errno otherwise.
+  */
+-__hidden extern int lxc_config_parse_arch(const char *arch, signed long *persona);
++extern int lxc_config_parse_arch(const char *arch, signed long *persona);
+ __hidden extern int lxc_clear_config_item(struct lxc_conf *c, const char *key);
diff --git a/utils/lxc/patches/007-storage_utils-unhide-and-rename-is_valid_storage_typ.patch b/utils/lxc/patches/007-storage_utils-unhide-and-rename-is_valid_storage_typ.patch
new file mode 100644 (file)
index 0000000..12fd6e0
--- /dev/null
@@ -0,0 +1,47 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sun, 18 Feb 2024 15:56:47 +0100
+Subject: [PATCH] storage_utils: unhide and rename is_valid_storage_type to
+ lxc_is_valid_storage_type
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit 6eb0a73e22027ff84b0768da0c2aec4029b7d143)
+---
+ src/lxc/storage/storage_utils.c | 2 +-
+ src/lxc/storage/storage_utils.h | 2 +-
+ src/lxc/tools/lxc_create.c      | 2 +-
+ 3 files changed, 3 insertions(+), 3 deletions(-)
+
+--- a/src/lxc/storage/storage_utils.c
++++ b/src/lxc/storage/storage_utils.c
+@@ -442,7 +442,7 @@ uint64_t get_fssize(char *s)
+       return ret;
+ }
+-bool is_valid_storage_type(const char *type)
++bool lxc_is_valid_storage_type(const char *type)
+ {
+       if (strcmp(type, "dir") == 0 ||
+           strcmp(type, "btrfs") == 0 ||
+--- a/src/lxc/storage/storage_utils.h
++++ b/src/lxc/storage/storage_utils.h
+@@ -37,7 +37,7 @@ __hidden extern const char *linkderef(co
+ __hidden extern bool unpriv_snap_allowed(struct lxc_storage *b, const char *t, bool snap,
+                                        bool maybesnap);
+ __hidden extern uint64_t get_fssize(char *s);
+-__hidden extern bool is_valid_storage_type(const char *type);
++extern bool lxc_is_valid_storage_type(const char *type);
+ __hidden extern int storage_destroy_wrapper(void *data);
+ #endif /* __LXC_STORAGE_UTILS_H */
+--- a/src/lxc/tools/lxc_create.c
++++ b/src/lxc/tools/lxc_create.c
+@@ -236,7 +236,7 @@ int main(int argc, char *argv[])
+       /* Final check whether the user gave use a valid bdev type. */
+       if (strncmp(my_args.bdevtype, "best", strlen(my_args.bdevtype)) != 0 &&
+           strncmp(my_args.bdevtype, "_unset", strlen(my_args.bdevtype)) != 0 &&
+-          !is_valid_storage_type(my_args.bdevtype)) {
++          !lxc_is_valid_storage_type(my_args.bdevtype)) {
+               ERROR("%s is not a valid backing storage type", my_args.bdevtype);
+               exit(EXIT_FAILURE);
+       }
diff --git a/utils/lxc/patches/008-storage_utils-move-get_fssize-to-utils.patch b/utils/lxc/patches/008-storage_utils-move-get_fssize-to-utils.patch
new file mode 100644 (file)
index 0000000..ebbec6f
--- /dev/null
@@ -0,0 +1,122 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sun, 18 Feb 2024 16:04:54 +0100
+Subject: [PATCH] storage_utils: move get_fssize to utils
+
+This helper is used in the lxc/tools and it's
+fully independent of storage_utils code, let's move it
+to utils.c
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit 9eee450d253650699a6f871695bfed1901679931)
+---
+ src/lxc/storage/storage_utils.c | 34 ---------------------------------
+ src/lxc/storage/storage_utils.h |  1 -
+ src/lxc/utils.c                 | 34 +++++++++++++++++++++++++++++++++
+ src/lxc/utils.h                 |  2 ++
+ 4 files changed, 36 insertions(+), 35 deletions(-)
+
+--- a/src/lxc/storage/storage_utils.c
++++ b/src/lxc/storage/storage_utils.c
+@@ -408,40 +408,6 @@ bool unpriv_snap_allowed(struct lxc_stor
+       return false;
+ }
+-uint64_t get_fssize(char *s)
+-{
+-      uint64_t ret;
+-      char *end;
+-
+-      ret = strtoull(s, &end, 0);
+-      if (end == s) {
+-              ERROR("Invalid blockdev size '%s', using default size", s);
+-              return 0;
+-      }
+-
+-      while (isblank(*end))
+-              end++;
+-
+-      if (*end == '\0') {
+-              ret *= 1024ULL * 1024ULL; /* MB by default */
+-      } else if (*end == 'b' || *end == 'B') {
+-              ret *= 1ULL;
+-      } else if (*end == 'k' || *end == 'K') {
+-              ret *= 1024ULL;
+-      } else if (*end == 'm' || *end == 'M') {
+-              ret *= 1024ULL * 1024ULL;
+-      } else if (*end == 'g' || *end == 'G') {
+-              ret *= 1024ULL * 1024ULL * 1024ULL;
+-      } else if (*end == 't' || *end == 'T') {
+-              ret *= 1024ULL * 1024ULL * 1024ULL * 1024ULL;
+-      } else {
+-              ERROR("Invalid blockdev unit size '%c' in '%s', using default size", *end, s);
+-              return 0;
+-      }
+-
+-      return ret;
+-}
+-
+ bool lxc_is_valid_storage_type(const char *type)
+ {
+       if (strcmp(type, "dir") == 0 ||
+--- a/src/lxc/storage/storage_utils.h
++++ b/src/lxc/storage/storage_utils.h
+@@ -36,7 +36,6 @@ __hidden extern int find_fstype_cb(char
+ __hidden extern const char *linkderef(const char *path, char *dest);
+ __hidden extern bool unpriv_snap_allowed(struct lxc_storage *b, const char *t, bool snap,
+                                        bool maybesnap);
+-__hidden extern uint64_t get_fssize(char *s);
+ extern bool lxc_is_valid_storage_type(const char *type);
+ __hidden extern int storage_destroy_wrapper(void *data);
+--- a/src/lxc/utils.c
++++ b/src/lxc/utils.c
+@@ -2150,3 +2150,37 @@ int print_r(int fd, const char *path)
+                    (st.st_mode & ~S_IFMT), st.st_uid, st.st_gid, maybe_empty(path));
+       return ret;
+ }
++
++uint64_t get_fssize(char *s)
++{
++      uint64_t ret;
++      char *end;
++
++      ret = strtoull(s, &end, 0);
++      if (end == s) {
++              ERROR("Invalid blockdev size '%s', using default size", s);
++              return 0;
++      }
++
++      while (isblank(*end))
++              end++;
++
++      if (*end == '\0') {
++              ret *= 1024ULL * 1024ULL; /* MB by default */
++      } else if (*end == 'b' || *end == 'B') {
++              ret *= 1ULL;
++      } else if (*end == 'k' || *end == 'K') {
++              ret *= 1024ULL;
++      } else if (*end == 'm' || *end == 'M') {
++              ret *= 1024ULL * 1024ULL;
++      } else if (*end == 'g' || *end == 'G') {
++              ret *= 1024ULL * 1024ULL * 1024ULL;
++      } else if (*end == 't' || *end == 'T') {
++              ret *= 1024ULL * 1024ULL * 1024ULL * 1024ULL;
++      } else {
++              ERROR("Invalid blockdev unit size '%c' in '%s', using default size", *end, s);
++              return 0;
++      }
++
++      return ret;
++}
+--- a/src/lxc/utils.h
++++ b/src/lxc/utils.h
+@@ -248,6 +248,8 @@ __hidden extern int safe_mount_beneath_a
+                                         const char *fstype, unsigned int flags, const void *data);
+ __hidden __lxc_unused int print_r(int fd, const char *path);
++__hidden extern uint64_t get_fssize(char *s);
++
+ static inline int copy_struct_from_client(__u32 server_size, void *dst,
+                                         __u32 client_size, const void *src)
+ {
diff --git a/utils/lxc/patches/009-network-use-IN_LIBLXC.patch b/utils/lxc/patches/009-network-use-IN_LIBLXC.patch
new file mode 100644 (file)
index 0000000..01b9bd7
--- /dev/null
@@ -0,0 +1,153 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sun, 18 Feb 2024 17:05:10 +0100
+Subject: [PATCH] network: use IN_LIBLXC
+
+Put a bunch of functions under #if IN_LIBLXC to compile-out
+them when network.c is linked with tools/tests code.
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit b90fecfda1474edbbe68abeb0b392ebeabd7f8d7)
+---
+ src/lxc/network.c | 33 +++++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+--- a/src/lxc/network.c
++++ b/src/lxc/network.c
+@@ -113,8 +113,13 @@ char *lxc_ifname_alnum_case_sensitive(ch
+       return template;
+ }
++
++#ifdef IN_LIBLXC
++
+ static const char loop_device[] = "lo";
++#endif /* IN_LIBLXC */
++
+ static int lxc_ip_route_dest(__u16 nlmsg_type, int family, int ifindex, void *dest, unsigned int netmask)
+ {
+       call_cleaner(nlmsg_free) struct nlmsg *answer = NULL, *nlmsg = NULL;
+@@ -172,6 +177,8 @@ static int lxc_ipv6_dest_add(int ifindex
+       return lxc_ip_route_dest(RTM_NEWROUTE, AF_INET6, ifindex, dest, netmask);
+ }
++#ifdef IN_LIBLXC
++
+ static int lxc_ipv4_dest_del(int ifindex, struct in_addr *dest, unsigned int netmask)
+ {
+       return lxc_ip_route_dest(RTM_DELROUTE, AF_INET, ifindex, dest, netmask);
+@@ -1267,6 +1274,8 @@ static netdev_configure_server_cb netdev
+       [LXC_NET_NONE]    = netdev_configure_server_none,
+ };
++#endif /* IN_LIBLXC */
++
+ static int __netdev_configure_container_common(struct lxc_netdev *netdev)
+ {
+       char current_ifname[IFNAMSIZ];
+@@ -1355,6 +1364,8 @@ static netdev_configure_container_cb net
+       [LXC_NET_NONE]    = netdev_configure_container_none,
+ };
++#ifdef IN_LIBLXC
++
+ static int netdev_shutdown_server_veth(struct lxc_handler *handler, struct lxc_netdev *netdev)
+ {
+       int ret;
+@@ -1496,6 +1507,8 @@ static netdev_shutdown_server_cb netdev_
+       [LXC_NET_NONE]    = netdev_shutdown_server_none,
+ };
++#endif /* IN_LIBLXC */
++
+ static int lxc_netdev_move_by_index_fd(int ifindex, int fd, const char *ifname)
+ {
+       call_cleaner(nlmsg_free) struct nlmsg *nlmsg = NULL;
+@@ -2360,6 +2373,8 @@ static int neigh_proxy_set(const char *i
+       return proc_sys_net_write(path, flag ? "1" : "0");
+ }
++#ifdef IN_LIBLXC
++
+ static int lxc_is_ip_neigh_proxy_enabled(const char *ifname, int family)
+ {
+       int ret;
+@@ -2378,6 +2393,8 @@ static int lxc_is_ip_neigh_proxy_enabled
+       return lxc_read_file_expect(path, buf, 1, "1");
+ }
++#endif /* IN_LIBLXC */
++
+ int lxc_neigh_proxy_on(const char *name, int family)
+ {
+       return neigh_proxy_set(name, family, 1);
+@@ -2901,6 +2918,8 @@ int lxc_find_gateway_addresses(struct lx
+       return 0;
+ }
++#ifdef IN_LIBLXC
++
+ #define LXC_USERNIC_PATH LIBEXECDIR "/lxc/lxc-user-nic"
+ static int lxc_create_network_unpriv_exec(const char *lxcpath,
+                                         const char *lxcname,
+@@ -3430,6 +3449,8 @@ static int lxc_create_network_priv(struc
+       return 0;
+ }
++#endif /* IN_LIBLXC */
++
+ /*
+  * LXC moves network devices into the target namespace based on their created
+  * name. The created name can either be randomly generated for e.g. veth
+@@ -3567,6 +3588,8 @@ static int network_requires_advanced_set
+       return true;
+ }
++#ifdef IN_LIBLXC
++
+ static int lxc_create_network_unpriv(struct lxc_handler *handler)
+ {
+       int hooks_version = handler->conf->hooks_version;
+@@ -3709,6 +3732,8 @@ clear_ifindices:
+       return true;
+ }
++#endif /* IN_LIBLXC */
++
+ int lxc_requests_empty_network(struct lxc_handler *handler)
+ {
+       struct list_head *netdevs = &handler->conf->netdevs;
+@@ -4152,6 +4177,8 @@ int lxc_network_recv_name_and_ifindex_fr
+       return 0;
+ }
++#ifdef IN_LIBLXC
++
+ void lxc_delete_network(struct lxc_handler *handler)
+ {
+       bool bret;
+@@ -4173,6 +4200,8 @@ void lxc_delete_network(struct lxc_handl
+               DEBUG("Deleted network devices");
+ }
++#endif /* IN_LIBLXC */
++
+ int lxc_netns_set_nsid(int fd)
+ {
+       int ret;
+@@ -4302,6 +4331,8 @@ int lxc_netns_get_nsid(int fd)
+       return -1;
+ }
++#ifdef IN_LIBLXC
++
+ int lxc_create_network(struct lxc_handler *handler)
+ {
+       int ret;
+@@ -4316,3 +4347,5 @@ int lxc_create_network(struct lxc_handle
+       return lxc_create_network_unpriv(handler);
+ }
++
++#endif /* IN_LIBLXC */
diff --git a/utils/lxc/patches/010-meson-link-with-liblxc-dynamically-everywhere-if-pos.patch b/utils/lxc/patches/010-meson-link-with-liblxc-dynamically-everywhere-if-pos.patch
new file mode 100644 (file)
index 0000000..9b6eacd
--- /dev/null
@@ -0,0 +1,217 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Sun, 18 Feb 2024 17:12:49 +0100
+Subject: [PATCH] meson: link with liblxc dynamically everywhere if possible
+
+Link tests/tools/commands dynamically with liblxc if possible.
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+(cherry picked from commit 86799f55422f31a0536c95639fe4b78fa7aa780f)
+---
+ src/lxc/cmd/meson.build   |  5 +++--
+ src/lxc/meson.build       | 43 +++++++++++++++++++++++++++++++++++++++
+ src/lxc/tools/meson.build | 26 ++++++++++++++++++-----
+ src/tests/meson.build     | 20 +++++++++++-------
+ 4 files changed, 80 insertions(+), 14 deletions(-)
+
+--- a/src/lxc/cmd/meson.build
++++ b/src/lxc/cmd/meson.build
+@@ -1,6 +1,6 @@
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+-cmd_common_sources = liblxc_sources + include_sources
++cmd_common_sources = liblxc_ext_sources + include_sources
+ cmd_lxc_init_sources = files(
+     'lxc_init.c',
+@@ -46,7 +46,7 @@ cmd_lxc_init_static_sources = files(
+     '../string_utils.c',
+     '../string_utils.h') + include_sources
+-cmd_lxc_monitord_sources = files('lxc_monitord.c') + cmd_common_sources + netns_ifaddrs_sources
++cmd_lxc_monitord_sources = files('lxc_monitord.c') + include_sources + netns_ifaddrs_sources
+ cmd_lxc_user_nic_sources = files('lxc_user_nic.c') + cmd_common_sources + netns_ifaddrs_sources
+ cmd_lxc_usernsexec_sources = files('lxc_usernsexec.c') + cmd_common_sources + netns_ifaddrs_sources
+@@ -89,6 +89,7 @@ cmd_programs += executable(
+     cmd_lxc_monitord_sources,
+     include_directories: liblxc_includes,
+     dependencies: liblxc_dep,
++    link_with: [liblxc_static],
+     install: true,
+     install_dir: lxclibexec)
+--- a/src/lxc/meson.build
++++ b/src/lxc/meson.build
+@@ -141,6 +141,49 @@ liblxc_sources = files(
+     'uuid.c',
+     'uuid.h')
++# subset of liblxc sources for internal users like tools/commands/tests
++liblxc_ext_sources = files(
++    'caps.c',
++    'caps.h',
++    'compiler.h',
++    'error.c',
++    'error.h',
++    'error_utils.h',
++    'file_utils.c',
++    'file_utils.h',
++    'hlist.h',
++    'idmap_utils.c',
++    'idmap_utils.h',
++    'initutils.c',
++    'initutils.h',
++    'list.h',
++    'log.c',
++    'log.h',
++    'mainloop.c',
++    'mainloop.h',
++    'namespace.c',
++    'namespace.h',
++    'network.c',
++    'network.h',
++    'nl.c',
++    'nl.h',
++    'parse.c',
++    'parse.h',
++    'open_utils.h',
++    'rexec.c',
++    'rexec.h',
++    'rtnl.c',
++    'rtnl.h',
++    'open_utils.h',
++    'process_utils.c',
++    'process_utils.h',
++    'string_utils.c',
++    'string_utils.h',
++    'syscall_numbers.h',
++    'syscall_wrappers.h',
++    'utils.c',
++    'utils.h')
++
+ if want_apparmor and libapparmor.found()
+     liblxc_sources += files('lsm/apparmor.c')
+ endif
+--- a/src/lxc/tools/meson.build
++++ b/src/lxc/tools/meson.build
+@@ -1,19 +1,35 @@
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+-tools_common_sources = liblxc_sources + files('arguments.c', 'arguments.h') + include_sources + netns_ifaddrs_sources
++tools_common_sources = files('arguments.c', 'arguments.h') + include_sources + netns_ifaddrs_sources
+-tools_commands = ['attach', 'autostart', 'cgroup', 'checkpoint', 'config',
++tools_commands_dynamic_link = ['attach', 'autostart', 'cgroup', 'checkpoint', 'config',
+     'console', 'copy', 'create', 'destroy', 'device', 'execute', 'freeze',
+-    'info', 'ls', 'monitor', 'snapshot', 'start', 'stop', 'top', 'unfreeze',
++    'info', 'ls', 'snapshot', 'start', 'stop', 'top', 'unfreeze',
+     'unshare', 'wait']
++tools_commands_static_link = ['monitor']
++
++tools_commands = tools_commands_dynamic_link + tools_commands_static_link
++
+ if want_tools
+-    foreach cmd : tools_commands 
++    foreach cmd : tools_commands_dynamic_link
++        public_programs += executable(
++            'lxc-' + cmd,
++            files('lxc_' + cmd + '.c') + tools_common_sources + liblxc_ext_sources,
++            dependencies: liblxc_dependencies,
++            include_directories: liblxc_includes,
++            c_args: ['-DNO_LXC_CONF'],
++            link_with: [liblxc],
++            install: true)
++    endforeach
++
++    foreach cmd : tools_commands_static_link
+         public_programs += executable(
+             'lxc-' + cmd,
+             files('lxc_' + cmd + '.c') + tools_common_sources,
++            dependencies: liblxc_dependencies,
+             include_directories: liblxc_includes,
+-            dependencies: liblxc_dep,
++            link_with: [liblxc_static],
+             install: true)
+     endforeach
+ endif
+--- a/src/tests/meson.build
++++ b/src/tests/meson.build
+@@ -1,6 +1,6 @@
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+-tests_common_sources = liblxc_sources + include_sources + netns_ifaddrs_sources
++tests_common_sources = liblxc_ext_sources + include_sources + netns_ifaddrs_sources
+ if want_tests
+     test_programs += executable(
+@@ -26,9 +26,10 @@ if want_tests
+     test_programs += executable(
+         'lxc-test-attach',
+-        files('attach.c') + tests_common_sources,
++        files('attach.c'),
+         include_directories: liblxc_includes,
+         dependencies: liblxc_dep,
++        link_with: [liblxc_static],
+         install: true)
+     test_programs += executable(
+@@ -47,9 +48,10 @@ if want_tests
+     test_programs += executable(
+         'lxc-test-cgpath',
+-        files('cgpath.c') + tests_common_sources,
++        files('cgpath.c'),
+         include_directories: liblxc_includes,
+         dependencies: liblxc_dep,
++        link_with: [liblxc_static],
+         install: true)
+     test_programs += executable(
+@@ -68,9 +70,10 @@ if want_tests
+     test_programs += executable(
+         'lxc-test-config-jump-table',
+-        files('config_jump_table.c') + tests_common_sources,
++        files('config_jump_table.c'),
+         include_directories: liblxc_includes,
+         dependencies: liblxc_dep,
++        link_with: [liblxc_static],
+         install: true)
+     test_programs += executable(
+@@ -152,16 +155,18 @@ if want_tests
+     test_programs += executable(
+         'lxc-test-locktests',
+-        files('locktests.c') + tests_common_sources,
++        files('locktests.c'),
+         include_directories: liblxc_includes,
+         dependencies: liblxc_dep,
++        link_with: [liblxc_static],
+         install: true)
+     test_programs += executable(
+         'lxc-test-utils',
+-        files('lxc-test-utils.c') + tests_common_sources,
++        files('lxc-test-utils.c'),
+         include_directories: liblxc_includes,
+         dependencies: liblxc_dep,
++        link_with: [liblxc_static],
+         install: true)
+     test_programs += executable(
+@@ -194,9 +199,10 @@ if want_tests
+     test_programs += executable(
+         'lxc-test-parse-config-file',
+-        files('parse_config_file.c') + tests_common_sources,
++        files('parse_config_file.c'),
+         include_directories: liblxc_includes,
+         dependencies: liblxc_dep,
++        link_with: [liblxc_static],
+         install: true)
+     test_programs += executable(