From c85a67a616217d121d128ecb8e634d7e25a83219 Mon Sep 17 00:00:00 2001 From: Serge Hallyn Date: Sat, 9 May 2026 15:03:08 +0000 Subject: [PATCH 01/16] Fix return from fn stack storage_copy builds a struct lxc_storage on the stack, and returns a pointer to that on success to its caller. That's could be a huge issue, except that its only caller quickly makes a copy of the -> src and then releases it. Still, to avoid a future user actually overwriting the struct and then continuing to use it, let's fix this now. There's no point returning the whole lxc_storage struct anyway. Just return a copy of the bdev->src. Signed-off-by: Serge Hallyn --- src/lxc/lxccontainer.c | 9 ++++----- src/lxc/storage/storage.c | 13 ++++++++----- src/lxc/storage/storage.h | 8 ++++---- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c index ea3a31bb4a..ba3d988b5b 100644 --- a/src/lxc/lxccontainer.c +++ b/src/lxc/lxccontainer.c @@ -3533,23 +3533,22 @@ static int copy_storage(struct lxc_container *c0, struct lxc_container *c, const char *newtype, int flags, const char *bdevdata, uint64_t newsize) { - struct lxc_storage *bdev; + char *bdev_src = NULL; bool need_rdep; if (should_default_to_snapshot(c0, c)) flags |= LXC_CLONE_SNAPSHOT; - bdev = storage_copy(c0, c->name, c->config_path, newtype, flags, + bdev_src = storage_copy(c0, c->name, c->config_path, newtype, flags, bdevdata, newsize, &need_rdep); - if (!bdev) { + if (!bdev_src) { ERROR("Error copying storage."); return -1; } /* Set new rootfs. */ free(c->lxc_conf->rootfs.path); - c->lxc_conf->rootfs.path = strdup(bdev->src); - storage_put(bdev); + c->lxc_conf->rootfs.path = bdev_src; if (!c->lxc_conf->rootfs.path) { ERROR("Out of memory while setting storage path."); diff --git a/src/lxc/storage/storage.c b/src/lxc/storage/storage.c index 2490d3c333..ba4674a4ba 100644 --- a/src/lxc/storage/storage.c +++ b/src/lxc/storage/storage.c @@ -296,12 +296,13 @@ bool storage_can_backup(struct lxc_conf *conf) /* If we're not snapshotting, then storage_copy becomes a simple case of mount * the original, mount the new, and rsync the contents. */ -struct lxc_storage *storage_copy(struct lxc_container *c, const char *cname, - const char *lxcpath, const char *bdevtype, - int flags, const char *bdevdata, - uint64_t newsize, bool *needs_rdep) +char *storage_copy(struct lxc_container *c, const char *cname, + const char *lxcpath, const char *bdevtype, + int flags, const char *bdevdata, + uint64_t newsize, bool *needs_rdep) { int ret; + char *ret_str; const char *src_no_prefix; struct lxc_storage *new, *orig; bool snap = (flags & LXC_CLONE_SNAPSHOT); @@ -520,7 +521,9 @@ struct lxc_storage *storage_copy(struct lxc_container *c, const char *cname, close_prot_errno_disarm(new_rootfs.dfd_idmapped); lxc_storage_put(c->lxc_conf); - return new; + ret_str = must_copy_string(new->src); + storage_put(new); + return ret_str; on_error_put_new: storage_put(new); diff --git a/src/lxc/storage/storage.h b/src/lxc/storage/storage.h index 148023bad1..2fb68b361f 100644 --- a/src/lxc/storage/storage.h +++ b/src/lxc/storage/storage.h @@ -103,10 +103,10 @@ struct lxc_storage { __hidden extern bool storage_lxc_is_dir(struct lxc_conf *conf); __hidden extern bool storage_can_backup(struct lxc_conf *conf); __hidden extern struct lxc_storage *storage_init(struct lxc_conf *conf); -__hidden extern struct lxc_storage *storage_copy(struct lxc_container *c, const char *cname, - const char *lxcpath, const char *bdevtype, - int flags, const char *bdevdata, uint64_t newsize, - bool *needs_rdep); +__hidden extern char *storage_copy(struct lxc_container *c, const char *cname, + const char *lxcpath, const char *bdevtype, + int flags, const char *bdevdata, uint64_t newsize, + bool *needs_rdep); __hidden extern struct lxc_storage *storage_create(const char *dest, const char *type, const char *cname, struct bdev_specs *specs, const struct lxc_conf *conf); From 432a17b5d439b894c6357ec36b22d8a7d45aa3fa Mon Sep 17 00:00:00 2001 From: Serge Hallyn Date: Sat, 9 May 2026 12:00:39 -0500 Subject: [PATCH 02/16] simplify cleanup in storage_copy struct lxc_rootfs has an lxc_storage pointer, and vice versa. put_lxc_rootfs(r) will free the storage it points to, but freeing the storage won't free the lxc_rootfs. By setting new_rootfs->storage and calling put_lxc_rootfs(new_rootfs), which we weren't doing, we can avoid having to manually close the dfd_idmapped. Signed-off-by: Serge Hallyn --- src/lxc/storage/storage.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lxc/storage/storage.c b/src/lxc/storage/storage.c index ba4674a4ba..25f6e353b1 100644 --- a/src/lxc/storage/storage.c +++ b/src/lxc/storage/storage.c @@ -320,6 +320,7 @@ char *storage_copy(struct lxc_container *c, const char *cname, .dfd_host = -EBADF, .fd_path_pin = -EBADF, .dfd_idmapped = -EBADF, + .storage = NULL, }; if (!src) { @@ -422,6 +423,7 @@ char *storage_copy(struct lxc_container *c, const char *cname, } TRACE("Initialized %s storage driver", new->type); new->rootfs = &new_rootfs; + new_rootfs.storage = new; /* create new paths */ ret = new->ops->clone_paths(orig, new, oldname, cname, oldpath, lxcpath, @@ -517,19 +519,16 @@ char *storage_copy(struct lxc_container *c, const char *cname, } on_success: - /* The only caller, copy_storage, doesn't ever close this. */ - close_prot_errno_disarm(new_rootfs.dfd_idmapped); + ret_str = must_copy_string(new->src); lxc_storage_put(c->lxc_conf); + put_lxc_rootfs(&new_rootfs, true); - ret_str = must_copy_string(new->src); - storage_put(new); return ret_str; on_error_put_new: - storage_put(new); + put_lxc_rootfs(&new_rootfs, true); on_error_put_orig: - close_prot_errno_disarm(new_rootfs.dfd_idmapped); lxc_storage_put(c->lxc_conf); return NULL; From 750da2a251637ed790ea8a671c1ed11594f24c1e Mon Sep 17 00:00:00 2001 From: Petr Malat Date: Mon, 4 Dec 2023 10:19:16 +0100 Subject: [PATCH 03/16] tools: lxc-info: Print memory and cpu utilization on cgroup2 system Emulate the cgroup1 behavior by calculating memory.kmem.usage_in_bytes and memory.usage_in_bytes using information provided in memory.stat and use cpu.stat/usage_usec instead of cpuacct.usage. Signed-off-by: Petr Malat [ alex: rebase & whitespace fix ] Signed-off-by: Alexander Mikhalitsyn --- src/lxc/tools/lxc_info.c | 172 ++++++++++++++++++++++++++++++++++----- 1 file changed, 153 insertions(+), 19 deletions(-) diff --git a/src/lxc/tools/lxc_info.c b/src/lxc/tools/lxc_info.c index 681b597521..cfcb6aff0a 100644 --- a/src/lxc/tools/lxc_info.c +++ b/src/lxc/tools/lxc_info.c @@ -16,6 +16,7 @@ #include "arguments.h" #include "log.h" #include "utils.h" +#include "macro.h" lxc_log_define(lxc_info, lxc); @@ -196,14 +197,158 @@ static void print_net_stats(struct lxc_container *c) } } -static void print_stats(struct lxc_container *c) +static int cg2_stat_iter(struct lxc_container *c, const char *file, void *cookie, + int (*iter)(char *key, char *val, void *cookie)) { - int i, ret; + char *fileptr, *lineptr, *line, *metric, *value; char buf[4096]; + int ret; + + ret = c->get_cgroup_item(c, file, buf, sizeof(buf)); + if (ret < 0) + return ret; + + if ((size_t)ret >= sizeof(buf)) { + fprintf(stderr, "Internal buffer too small to read '%s'\n", file); + return -EMSGSIZE; + } + + for (line = strtok_r(buf, "\n", &fileptr); line; line = strtok_r(NULL, "\n", &fileptr)) { + metric = strtok_r(line, " ", &lineptr); + if (!metric) + goto err; + + value = strtok_r(NULL, " ", &lineptr); + if (!value) + goto err; + + ret = iter(metric, value, cookie); + if (ret) + return ret; + } + + return 0; + +err: + fprintf(stderr, "Unexpected syntax of memory.stat\n"); + return -EIO; +} + +static int cg1_cpu_usage(struct lxc_container *c, char *usage) +{ + char buf[64]; + int ret; ret = c->get_cgroup_item(c, "cpuacct.usage", buf, sizeof(buf)); if (ret > 0 && (size_t)ret < sizeof(buf)) { str_chomp(buf); + strcpy(usage, buf); + return 0; + } + + return ret; +} + +static int cg2_cpu_usage_iter(char *metric, char *value, void *cookie) +{ + if (strcmp(metric, "usage_usec")) + return 0; + + strcpy(cookie, value); + strcat(cookie, "000"); + return 1; +} + +static int cg2_cpu_usage(struct lxc_container *c, char *usage) +{ + int ret; + + ret = cg2_stat_iter(c, "cpu.stat", usage, cg2_cpu_usage_iter); + return ret == 1 ? 0 : ret ?: -ESRCH; +} + +static int search_array(const char *array[], const char *needle, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) + if (!strcmp(array[i], needle)) + return 1; + return 0; +} + +struct cg2_mem_usage_s { + unsigned long long user, kernel; +}; + +static int cg2_mem_usage_iter(char *metric, char *value, void *cookie) +{ + const char *kernel_fields[] = { "kernel_stack", "pagetables", "sock", "slab" }; + const char *user_fields[] = { "anon", "file" }; + struct cg2_mem_usage_s *usage = cookie; + unsigned long long numvalue; + char *end; + + numvalue = strtoull(value, &end, 10); + if (numvalue == ULLONG_MAX || *end) { + fprintf(stderr, "Unexpected syntax of memory.stat\n"); + return -EIO; + } + + if (search_array(kernel_fields, metric, ARRAY_SIZE(kernel_fields))) + usage->kernel += numvalue; + else if (search_array(user_fields, metric, ARRAY_SIZE(user_fields))) + usage->user += numvalue; + return 0; +} + +static int cg2_mem_usage(struct lxc_container *c, char *user, char *kernel) +{ + struct cg2_mem_usage_s usage = { 0 }; + int ret; + + ret = cg2_stat_iter(c, "memory.stat", &usage, cg2_mem_usage_iter); + if (ret < 0) + return ret; + + sprintf(kernel, "%llu", usage.kernel); + sprintf(user, "%llu", usage.user); + + return 0; +} + +static int cg1_mem_usage(struct lxc_container *c, char *user, char *kernel) +{ + struct { + const char *file; + char *target; + } lxstat[] = { + { "memory.usage_in_bytes", user }, + { "memory.kmem.usage_in_bytes", kernel }, + }; + int ret, i, match = 0; + char buf[64]; + + for (i = 0; i < (int)ARRAY_SIZE(lxstat); i++) { + ret = c->get_cgroup_item(c, lxstat[i].file, buf, sizeof(buf)); + if (ret > 0 && (size_t)ret < sizeof(buf)) { + str_chomp(buf); + strcpy(lxstat[i].target, buf); + match++; + } + } + + return match == ARRAY_SIZE(lxstat) ? 0 : -ESRCH; +} + +static void print_stats(struct lxc_container *c) +{ + int ret; + char buf[4096]; + char *user = buf; + char *kernel = buf + sizeof(buf) / 2; + + if (!cg1_cpu_usage(c, buf) || !cg2_cpu_usage(c, buf)) { if (humanize) { float seconds = strtof(buf, NULL) / 1000000000.0; printf("%-15s %.2f seconds\n", "CPU use:", seconds); @@ -233,23 +378,12 @@ static void print_stats(struct lxc_container *c) fflush(stdout); } - static const struct { - const char *name; - const char *file; - } lxstat[] = { - { "Memory use:", "memory.usage_in_bytes" }, - { "KMem use:", "memory.kmem.usage_in_bytes" }, - { NULL, NULL }, - }; - - for (i = 0; lxstat[i].name; i++) { - ret = c->get_cgroup_item(c, lxstat[i].file, buf, sizeof(buf)); - if (ret > 0 && (size_t)ret < sizeof(buf)) { - str_chomp(buf); - str_size_humanize(buf, sizeof(buf)); - printf("%-15s %s\n", lxstat[i].name, buf); - fflush(stdout); - } + if (!cg1_mem_usage(c, user, kernel) || !cg2_mem_usage(c, user, kernel)) { + str_size_humanize(user, sizeof(buf) / 2 - 1); + printf("%-15s %s\n", "Memory use:", user); + str_size_humanize(kernel, sizeof(buf) / 2 - 1); + printf("%-15s %s\n", "KMem use:", kernel); + fflush(stdout); } } From d80b6bf16043b9be9d93e47fe21baa8953b9f5f7 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Wed, 24 Jul 2024 15:23:11 +0200 Subject: [PATCH 04/16] tools: lxc-top: refactor lxc-top stat structs Create separate structs for each controller class. This will make it easier to add cgroupv2 support. No functional changes. Signed-off-by: Natanael Copa --- src/lxc/tools/lxc_top.c | 125 +++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 53 deletions(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index aa6e7209e3..bcc695b3e0 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -35,16 +35,24 @@ struct blkio_stats { uint64_t total; }; -struct stats { - uint64_t mem_used; - uint64_t mem_limit; - uint64_t memsw_used; - uint64_t memsw_limit; +struct cpu_stats { + uint64_t use_nanos; + uint64_t use_user; + uint64_t use_sys; +}; + +struct mem_stats { + uint64_t used; + uint64_t limit; + uint64_t swap_used; + uint64_t swap_limit; uint64_t kmem_used; uint64_t kmem_limit; - uint64_t cpu_use_nanos; - uint64_t cpu_use_user; - uint64_t cpu_use_sys; +}; + +struct stats { + struct mem_stats mem; + struct cpu_stats cpu; struct blkio_stats io_service_bytes; struct blkio_stats io_serviced; }; @@ -314,34 +322,45 @@ static void stat_get_blk_stats(struct lxc_container *c, const char *item, return; } +static void cg1_mem_stats(struct lxc_container *c, struct mem_stats *mem) +{ + mem->used = stat_get_int(c, "memory.usage_in_bytes"); + mem->limit = stat_get_int(c, "memory.limit_in_bytes"); + mem->swap_used = stat_get_int(c, "memory.memsw.usage_in_bytes"); + mem->swap_limit = stat_get_int(c, "memory.memsw.limit_in_bytes"); + mem->kmem_used = stat_get_int(c, "memory.kmem.usage_in_bytes"); + mem->kmem_limit = stat_get_int(c, "memory.kmem.limit_in_bytes"); +} + +static void cg1_cpu_stats(struct lxc_container *c, struct cpu_stats *cpu) +{ + cpu->use_nanos = stat_get_int(c, "cpuacct.usage"); + cpu->use_user = stat_match_get_int(c, "cpuacct.stat", "user", 1); + cpu->use_sys = stat_match_get_int(c, "cpuacct.stat", "system", 1); +} + static void stats_get(struct lxc_container *c, struct container_stats *ct, struct stats *total) { ct->c = c; - ct->stats->mem_used = stat_get_int(c, "memory.usage_in_bytes"); - ct->stats->mem_limit = stat_get_int(c, "memory.limit_in_bytes"); - ct->stats->memsw_used = stat_get_int(c, "memory.memsw.usage_in_bytes"); - ct->stats->memsw_limit = stat_get_int(c, "memory.memsw.limit_in_bytes"); - ct->stats->kmem_used = stat_get_int(c, "memory.kmem.usage_in_bytes"); - ct->stats->kmem_limit = stat_get_int(c, "memory.kmem.limit_in_bytes"); - ct->stats->cpu_use_nanos = stat_get_int(c, "cpuacct.usage"); - ct->stats->cpu_use_user = stat_match_get_int(c, "cpuacct.stat", "user", 1); - ct->stats->cpu_use_sys = stat_match_get_int(c, "cpuacct.stat", "system", 1); - + cg1_mem_stats(c, &ct->stats->mem); + cg1_cpu_stats(c, &ct->stats->cpu); stat_get_blk_stats(c, "blkio.throttle.io_service_bytes", &ct->stats->io_service_bytes); stat_get_blk_stats(c, "blkio.throttle.io_serviced", &ct->stats->io_serviced); if (total) { - total->mem_used = total->mem_used + ct->stats->mem_used; - total->mem_limit = total->mem_limit + ct->stats->mem_limit; - total->memsw_used = total->memsw_used + ct->stats->memsw_used; - total->memsw_limit = total->memsw_limit + ct->stats->memsw_limit; - total->kmem_used = total->kmem_used + ct->stats->kmem_used; - total->kmem_limit = total->kmem_limit + ct->stats->kmem_limit; - total->cpu_use_nanos = total->cpu_use_nanos + ct->stats->cpu_use_nanos; - total->cpu_use_user = total->cpu_use_user + ct->stats->cpu_use_user; - total->cpu_use_sys = total->cpu_use_sys + ct->stats->cpu_use_sys; + total->mem.used += ct->stats->mem.used; + total->mem.limit += ct->stats->mem.limit; + total->mem.swap_used += ct->stats->mem.swap_used; + total->mem.swap_limit += ct->stats->mem.swap_limit; + total->mem.kmem_used += ct->stats->mem.kmem_used; + total->mem.kmem_limit += ct->stats->mem.kmem_limit; + + total->cpu.use_nanos += ct->stats->cpu.use_nanos; + total->cpu.use_user += ct->stats->cpu.use_user; + total->cpu.use_sys += ct->stats->cpu.use_sys; + total->io_service_bytes.total += ct->stats->io_service_bytes.total; - total->io_service_bytes.read += ct->stats->io_service_bytes.read; + total->io_service_bytes.read += ct->stats->io_service_bytes.read; total->io_service_bytes.write += ct->stats->io_service_bytes.write; } } @@ -351,19 +370,19 @@ static void stats_print_header(struct stats *stats) printf(TERMRVRS TERMBOLD); printf("%-18s %12s %12s %12s %36s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem"); - if (stats->memsw_used > 0) + if (stats->mem.swap_used > 0) printf(" %10s", "MemSw"); - if (stats->kmem_used > 0) + if (stats->mem.kmem_used > 0) printf(" %10s", "KMem"); printf("\n"); printf("%-18s %12s %12s %12s %36s %10s", "Name", "Used", "Sys", "User", "Total(Read/Write)", "Used"); - if (stats->memsw_used > 0) + if (stats->mem.swap_used > 0) printf(" %10s", "Used"); - if (stats->kmem_used > 0) + if (stats->mem.kmem_used > 0) printf(" %10s", "Used"); printf("\n"); @@ -388,7 +407,7 @@ static void stats_print(const char *name, const struct stats *stats, size_humanize(stats->io_service_bytes.total, iosb_total_str, sizeof(iosb_total_str)); size_humanize(stats->io_service_bytes.read, iosb_read_str, sizeof(iosb_read_str)); size_humanize(stats->io_service_bytes.write, iosb_write_str, sizeof(iosb_write_str)); - size_humanize(stats->mem_used, mem_used_str, sizeof(mem_used_str)); + size_humanize(stats->mem.used, mem_used_str, sizeof(mem_used_str)); ret = snprintf(iosb_str, sizeof(iosb_str), "%s(%s/%s)", iosb_total_str, iosb_read_str, iosb_write_str); if (ret < 0 || (size_t)ret >= sizeof(iosb_str)) @@ -396,18 +415,18 @@ static void stats_print(const char *name, const struct stats *stats, printf("%-18.18s %12.2f %12.2f %12.2f %36s %10s", name, - (float)stats->cpu_use_nanos / 1000000000, - (float)stats->cpu_use_sys / USER_HZ, - (float)stats->cpu_use_user / USER_HZ, + (float)stats->cpu.use_nanos / 1000000000, + (float)stats->cpu.use_sys / USER_HZ, + (float)stats->cpu.use_user / USER_HZ, iosb_str, mem_used_str); - if (total->memsw_used > 0) { - size_humanize(stats->memsw_used, memsw_used_str, sizeof(memsw_used_str)); + if (total->mem.swap_used > 0) { + size_humanize(stats->mem.swap_used, memsw_used_str, sizeof(memsw_used_str)); printf(" %10s", memsw_used_str); } - if (total->kmem_used > 0) { - size_humanize(stats->kmem_used, kmem_used_str, sizeof(kmem_used_str)); + if (total->mem.kmem_used > 0) { + size_humanize(stats->mem.kmem_used, kmem_used_str, sizeof(kmem_used_str)); printf(" %10s", kmem_used_str); } } else { @@ -415,11 +434,11 @@ static void stats_print(const char *name, const struct stats *stats, time_ms = (unsigned long long) (time_val.tv_sec) * 1000 + (unsigned long long) (time_val.tv_usec) / 1000; printf("%" PRIu64 ",%s,%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64, - (uint64_t)time_ms, name, (uint64_t)stats->cpu_use_nanos, - (uint64_t)stats->cpu_use_sys, - (uint64_t)stats->cpu_use_user, (uint64_t)stats->io_service_bytes.total, - (uint64_t)stats->io_serviced.total, (uint64_t)stats->mem_used, - (uint64_t)stats->memsw_used, (uint64_t)stats->kmem_used); + (uint64_t)time_ms, name, (uint64_t)stats->cpu.use_nanos, + (uint64_t)stats->cpu.use_sys, + (uint64_t)stats->cpu.use_user, (uint64_t)stats->io_service_bytes.total, + (uint64_t)stats->io_serviced.total, (uint64_t)stats->mem.used, + (uint64_t)stats->mem.swap_used, (uint64_t)stats->mem.kmem_used); } } @@ -441,9 +460,9 @@ static int cmp_cpuuse(const void *sct1, const void *sct2) const struct container_stats *ct2 = sct2; if (sort_reverse) - return ct2->stats->cpu_use_nanos < ct1->stats->cpu_use_nanos; + return ct2->stats->cpu.use_nanos < ct1->stats->cpu.use_nanos; - return ct1->stats->cpu_use_nanos < ct2->stats->cpu_use_nanos; + return ct1->stats->cpu.use_nanos < ct2->stats->cpu.use_nanos; } static int cmp_blkio(const void *sct1, const void *sct2) @@ -463,9 +482,9 @@ static int cmp_memory(const void *sct1, const void *sct2) const struct container_stats *ct2 = sct2; if (sort_reverse) - return ct2->stats->mem_used < ct1->stats->mem_used; + return ct2->stats->mem.used < ct1->stats->mem.used; - return ct1->stats->mem_used < ct2->stats->mem_used; + return ct1->stats->mem.used < ct2->stats->mem.used; } static int cmp_memorysw(const void *sct1, const void *sct2) @@ -474,9 +493,9 @@ static int cmp_memorysw(const void *sct1, const void *sct2) const struct container_stats *ct2 = sct2; if (sort_reverse) - return ct2->stats->memsw_used < ct1->stats->memsw_used; + return ct2->stats->mem.swap_used < ct1->stats->mem.swap_used; - return ct1->stats->memsw_used < ct2->stats->memsw_used; + return ct1->stats->mem.swap_used < ct2->stats->mem.swap_used; } static int cmp_kmemory(const void *sct1, const void *sct2) @@ -485,9 +504,9 @@ static int cmp_kmemory(const void *sct1, const void *sct2) const struct container_stats *ct2 = sct2; if (sort_reverse) - return ct2->stats->kmem_used < ct1->stats->kmem_used; + return ct2->stats->mem.kmem_used < ct1->stats->mem.kmem_used; - return ct1->stats->kmem_used < ct2->stats->kmem_used; + return ct1->stats->mem.kmem_used < ct2->stats->mem.kmem_used; } static void ct_sort(int active) From 46ad85a59f1f96001d524ed008261eab48b1242a Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Wed, 24 Jul 2024 15:47:55 +0200 Subject: [PATCH 05/16] tools: lxc-top: get memory stats from cgroups2 Signed-off-by: Natanael Copa --- src/lxc/tools/lxc_top.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index bcc695b3e0..29d81563ff 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -212,7 +212,6 @@ static uint64_t stat_get_int(struct lxc_container *c, const char *item) len = c->get_cgroup_item(c, item, buf, sizeof(buf)); if (len <= 0) { - fprintf(stderr, "Unable to read cgroup item %s\n", item); return 0; } @@ -322,7 +321,7 @@ static void stat_get_blk_stats(struct lxc_container *c, const char *item, return; } -static void cg1_mem_stats(struct lxc_container *c, struct mem_stats *mem) +static int cg1_mem_stats(struct lxc_container *c, struct mem_stats *mem) { mem->used = stat_get_int(c, "memory.usage_in_bytes"); mem->limit = stat_get_int(c, "memory.limit_in_bytes"); @@ -330,6 +329,20 @@ static void cg1_mem_stats(struct lxc_container *c, struct mem_stats *mem) mem->swap_limit = stat_get_int(c, "memory.memsw.limit_in_bytes"); mem->kmem_used = stat_get_int(c, "memory.kmem.usage_in_bytes"); mem->kmem_limit = stat_get_int(c, "memory.kmem.limit_in_bytes"); + return mem->used > 0 ? 0 : -1; +} + +static int cg2_mem_stats(struct lxc_container *c, struct mem_stats *mem) +{ + mem->used = stat_get_int(c, "memory.current"); + mem->limit = stat_get_int(c, "memory.max"); + mem->swap_used = stat_get_int(c, "memory.swap.current"); + mem->swap_limit = stat_get_int(c, "memory.swap.max"); + /* TODO: find the kernel usage */ + mem->kmem_used = 0; + /* does not exist in cgroup v2 */ + mem->kmem_limit = 0; + return mem->used > 0 ? 0 : -1; } static void cg1_cpu_stats(struct lxc_container *c, struct cpu_stats *cpu) @@ -342,7 +355,11 @@ static void cg1_cpu_stats(struct lxc_container *c, struct cpu_stats *cpu) static void stats_get(struct lxc_container *c, struct container_stats *ct, struct stats *total) { ct->c = c; - cg1_mem_stats(c, &ct->stats->mem); + if (cg1_mem_stats(c, &ct->stats->mem) < 0) { + if (cg2_mem_stats(c, &ct->stats->mem) < 0) { + fprintf(stderr, "Unable to read memory stats\n"); + } + } cg1_cpu_stats(c, &ct->stats->cpu); stat_get_blk_stats(c, "blkio.throttle.io_service_bytes", &ct->stats->io_service_bytes); stat_get_blk_stats(c, "blkio.throttle.io_serviced", &ct->stats->io_serviced); From 25b8560529a5e8a27582537415af5bfff5b8ad74 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Wed, 24 Jul 2024 16:32:30 +0200 Subject: [PATCH 06/16] lxc-top: CPU stats for cgroups2 Recalculate the usec to nanoseconds and USER_HZ Signed-off-by: Natanael Copa --- src/lxc/tools/lxc_top.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index 29d81563ff..3a6c49e82f 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -230,7 +230,6 @@ static uint64_t stat_match_get_int(struct lxc_container *c, const char *item, len = c->get_cgroup_item(c, item, buf, sizeof(buf)); if (len <= 0) { - fprintf(stderr, "Unable to read cgroup item %s\n", item); goto out; } @@ -345,11 +344,22 @@ static int cg2_mem_stats(struct lxc_container *c, struct mem_stats *mem) return mem->used > 0 ? 0 : -1; } -static void cg1_cpu_stats(struct lxc_container *c, struct cpu_stats *cpu) +static int cg1_cpu_stats(struct lxc_container *c, struct cpu_stats *cpu) { cpu->use_nanos = stat_get_int(c, "cpuacct.usage"); cpu->use_user = stat_match_get_int(c, "cpuacct.stat", "user", 1); cpu->use_sys = stat_match_get_int(c, "cpuacct.stat", "system", 1); + return cpu->use_nanos > 0 ? 0 : -1; +} + +static int cg2_cpu_stats(struct lxc_container *c, struct cpu_stats *cpu) +{ + /* convert microseconds to nanoseconds */ + cpu->use_nanos = stat_match_get_int(c, "cpu.stat", "usage_usec", 1) * 1000; + + cpu->use_user = stat_match_get_int(c, "cpu.stat", "user_usec", 1) * USER_HZ / 1000000; + cpu->use_sys = stat_match_get_int(c, "cpu.stat", "system_usec", 1) * USER_HZ / 1000000; + return cpu->use_nanos > 0 ? 0 : -1; } static void stats_get(struct lxc_container *c, struct container_stats *ct, struct stats *total) @@ -360,7 +370,12 @@ static void stats_get(struct lxc_container *c, struct container_stats *ct, struc fprintf(stderr, "Unable to read memory stats\n"); } } - cg1_cpu_stats(c, &ct->stats->cpu); + if (cg1_cpu_stats(c, &ct->stats->cpu) < 0) { + if (cg2_cpu_stats(c, &ct->stats->cpu) < 0) { + fprintf(stderr, "Unable to read CPU stats\n"); + } + } + stat_get_blk_stats(c, "blkio.throttle.io_service_bytes", &ct->stats->io_service_bytes); stat_get_blk_stats(c, "blkio.throttle.io_serviced", &ct->stats->io_serviced); From 54bebd3b5f3209698f6727bd620e06c1880fb929 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Wed, 24 Jul 2024 16:54:00 +0200 Subject: [PATCH 07/16] tools: lxc-top: get the user HZ at runtime The USER_HZ depends on the kernel configuration. Get it run-time instead of assume it is 100 HZ. Signed-off-by: Natanael Copa --- src/lxc/tools/lxc_top.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index 3a6c49e82f..deea1b41d6 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -22,7 +22,6 @@ #include "mainloop.h" #include "utils.h" -#define USER_HZ 100 #define ESC "\033" #define TERMCLEAR ESC "[H" ESC "[J" #define TERMNORM ESC "[0m" @@ -70,6 +69,7 @@ static int sort_reverse = 0; static struct termios oldtios; static struct container_stats *container_stats = NULL; static int ct_alloc_cnt = 0; +static long user_hz = 0; static int my_parser(struct lxc_arguments *args, int c, char *arg) { @@ -357,8 +357,8 @@ static int cg2_cpu_stats(struct lxc_container *c, struct cpu_stats *cpu) /* convert microseconds to nanoseconds */ cpu->use_nanos = stat_match_get_int(c, "cpu.stat", "usage_usec", 1) * 1000; - cpu->use_user = stat_match_get_int(c, "cpu.stat", "user_usec", 1) * USER_HZ / 1000000; - cpu->use_sys = stat_match_get_int(c, "cpu.stat", "system_usec", 1) * USER_HZ / 1000000; + cpu->use_user = stat_match_get_int(c, "cpu.stat", "user_usec", 1) * user_hz / 1000000; + cpu->use_sys = stat_match_get_int(c, "cpu.stat", "system_usec", 1) * user_hz / 1000000; return cpu->use_nanos > 0 ? 0 : -1; } @@ -448,8 +448,8 @@ static void stats_print(const char *name, const struct stats *stats, printf("%-18.18s %12.2f %12.2f %12.2f %36s %10s", name, (float)stats->cpu.use_nanos / 1000000000, - (float)stats->cpu.use_sys / USER_HZ, - (float)stats->cpu.use_user / USER_HZ, + (float)stats->cpu.use_sys / user_hz, + (float)stats->cpu.use_user / user_hz, iosb_str, mem_used_str); @@ -640,6 +640,11 @@ int lxc_top_main(int argc, char *argv[]) signal(SIGINT, sig_handler); signal(SIGQUIT, sig_handler); + user_hz = sysconf(_SC_CLK_TCK); + if (user_hz == 0) { + user_hz = 100; + } + if (lxc_mainloop_open(&descr)) { fprintf(stderr, "Failed to create mainloop\n"); goto out; From 1722580c9872aae2589c4ff5d18e54cacab815a7 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Wed, 24 Jul 2024 17:29:10 +0200 Subject: [PATCH 08/16] tools: lxc-top: add cgroups2 IO stats Signed-off-by: Natanael Copa --- src/lxc/tools/lxc_top.c | 60 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index deea1b41d6..15cf75a9a0 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -281,21 +281,21 @@ static uint64_t stat_match_get_int(struct lxc_container *c, const char *item, 8:0 Total 149327872 Total 149327872 */ -static void stat_get_blk_stats(struct lxc_container *c, const char *item, +static int cg1_get_blk_stats(struct lxc_container *c, const char *item, struct blkio_stats *stats) { char buf[4096]; int i, len; char **lines, **cols; + int ret = -1; len = c->get_cgroup_item(c, item, buf, sizeof(buf)); if (len <= 0 || (size_t)len >= sizeof(buf)) { - fprintf(stderr, "Unable to read cgroup item %s\n", item); - return; + return ret; } lines = lxc_string_split_and_trim(buf, '\n'); if (!lines) - return; + return ret; memset(stats, 0, sizeof(struct blkio_stats)); @@ -314,10 +314,50 @@ static void stat_get_blk_stats(struct lxc_container *c, const char *item, lxc_free_array((void **)cols, free); } + ret = 0; +out: + lxc_free_array((void **)lines, free); + return ret; +} + +static int cg2_get_blk_stats(struct lxc_container *c, const char *item, + struct blkio_stats *stats) { + char buf[4096]; + int i, j, len; + char **lines, **cols; + int ret = -1; + len = c->get_cgroup_item(c, item, buf, sizeof(buf)); + if (len <= 0 || (size_t)len >= sizeof(buf)) { + return ret; + } + + lines = lxc_string_split_and_trim(buf, '\n'); + if (!lines) + return ret; + + memset(stats, 0, sizeof(struct blkio_stats)); + + for (i = 0; lines[i]; i++) { + cols = lxc_string_split_and_trim(lines[i], ' '); + if (!cols) + goto out; + + for (j = 0; cols[j]; j++) { + if (strncmp(cols[j], "rbytes=", 7) == 0) { + stats->read += strtoull(&cols[j][7], NULL, 0); + } else if (strncmp(cols[j], "wbytes=", 7) == 0) { + stats->write += strtoull(&cols[j][7], NULL, 0); + } + } + + lxc_free_array((void **)cols, free); + } + stats->total = stats->read + stats->write; + ret = 0; out: lxc_free_array((void **)lines, free); - return; + return ret; } static int cg1_mem_stats(struct lxc_container *c, struct mem_stats *mem) @@ -376,8 +416,14 @@ static void stats_get(struct lxc_container *c, struct container_stats *ct, struc } } - stat_get_blk_stats(c, "blkio.throttle.io_service_bytes", &ct->stats->io_service_bytes); - stat_get_blk_stats(c, "blkio.throttle.io_serviced", &ct->stats->io_serviced); + if (cg1_get_blk_stats(c, "blkio.throttle.io_service_bytes", &ct->stats->io_service_bytes) < 0) { + if (cg2_get_blk_stats(c, "io.stat", &ct->stats->io_service_bytes) < 0) { + fprintf(stderr, "Unable to read IO stats\n"); + } + } else { + /* only with cgroups v1 */ + cg1_get_blk_stats(c, "blkio.throttle.io_serviced", &ct->stats->io_serviced); + } if (total) { total->mem.used += ct->stats->mem.used; From 0ef8abe9fa7950f4889ea36919a3f1a01f171191 Mon Sep 17 00:00:00 2001 From: Devon Schwartz Date: Wed, 24 Jul 2024 22:17:32 -0500 Subject: [PATCH 09/16] lxc-top: added kernel memory usage for cgroup2 Signed-off-by: Devon Schwartz --- src/lxc/tools/lxc_top.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index 15cf75a9a0..6cb38bb906 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -373,14 +373,13 @@ static int cg1_mem_stats(struct lxc_container *c, struct mem_stats *mem) static int cg2_mem_stats(struct lxc_container *c, struct mem_stats *mem) { - mem->used = stat_get_int(c, "memory.current"); - mem->limit = stat_get_int(c, "memory.max"); - mem->swap_used = stat_get_int(c, "memory.swap.current"); - mem->swap_limit = stat_get_int(c, "memory.swap.max"); - /* TODO: find the kernel usage */ - mem->kmem_used = 0; + mem->used = stat_get_int(c, "memory.current"); + mem->limit = stat_get_int(c, "memory.max"); + mem->swap_used = stat_get_int(c, "memory.swap.current"); + mem->swap_limit = stat_get_int(c, "memory.swap.max"); + mem->kmem_used = stat_match_get_int(c, "memory.stat", "kernel", 1); /* does not exist in cgroup v2 */ - mem->kmem_limit = 0; + // mem->kmem_limit = 0; return mem->used > 0 ? 0 : -1; } From 5aef4b9e9349b5deee68ddc949c381eb1c58ce6e Mon Sep 17 00:00:00 2001 From: Devon Schwartz Date: Thu, 25 Jul 2024 16:29:48 -0500 Subject: [PATCH 10/16] tools: lxc-top: add batch as conditional because ct_print_cnt irrelevant for lxc-top -b Signed-off-by: Devon Schwartz --- src/lxc/tools/lxc_top.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index 6cb38bb906..e10ab757e2 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -732,7 +732,7 @@ int lxc_top_main(int argc, char *argv[]) stats_print_header(&total); } - for (i = 0; i < active_cnt && i < ct_print_cnt; i++) { + for (i = 0; i < active_cnt && i < (ct_print_cnt || batch); i++) { stats_print(container_stats[i].c->name, container_stats[i].stats, &total); printf("\n"); } From 1f552e114b1f4a9c1d2435a0058e1ba05e6785b2 Mon Sep 17 00:00:00 2001 From: Devon Schwartz Date: Fri, 26 Jul 2024 14:31:40 -0500 Subject: [PATCH 11/16] tools: lxc-top: print when batch or less than container count Signed-off-by: Devon Schwartz --- src/lxc/tools/lxc_top.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index e10ab757e2..3d6ee5cbad 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -732,7 +732,7 @@ int lxc_top_main(int argc, char *argv[]) stats_print_header(&total); } - for (i = 0; i < active_cnt && i < (ct_print_cnt || batch); i++) { + for (i = 0; i < active_cnt && ((i < ct_print_cnt) || batch); i++) { stats_print(container_stats[i].c->name, container_stats[i].stats, &total); printf("\n"); } From 997683582d7905a89e4750b95700fc472657c693 Mon Sep 17 00:00:00 2001 From: Devon Schwartz Date: Fri, 26 Jul 2024 15:46:53 -0500 Subject: [PATCH 12/16] tools: lxc-top: add union to cgroup1 memsw and cgroup2 swap stats Signed-off-by: Devon Schwartz --- src/lxc/tools/lxc_top.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index 3d6ee5cbad..e67349d2a5 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -41,10 +41,18 @@ struct cpu_stats { }; struct mem_stats { + union { + struct { + uint64_t swap_used; + uint64_t swap_limit; + }; /* v2 only */ + struct { + uint64_t memsw_used; + uint64_t memsw_limit; + }; /* v1 only */ + }; uint64_t used; uint64_t limit; - uint64_t swap_used; - uint64_t swap_limit; uint64_t kmem_used; uint64_t kmem_limit; }; @@ -362,12 +370,12 @@ static int cg2_get_blk_stats(struct lxc_container *c, const char *item, static int cg1_mem_stats(struct lxc_container *c, struct mem_stats *mem) { - mem->used = stat_get_int(c, "memory.usage_in_bytes"); - mem->limit = stat_get_int(c, "memory.limit_in_bytes"); - mem->swap_used = stat_get_int(c, "memory.memsw.usage_in_bytes"); - mem->swap_limit = stat_get_int(c, "memory.memsw.limit_in_bytes"); - mem->kmem_used = stat_get_int(c, "memory.kmem.usage_in_bytes"); - mem->kmem_limit = stat_get_int(c, "memory.kmem.limit_in_bytes"); + mem->used = stat_get_int(c, "memory.usage_in_bytes"); + mem->limit = stat_get_int(c, "memory.limit_in_bytes"); + mem->memsw_used = stat_get_int(c, "memory.memsw.usage_in_bytes"); + mem->memsw_limit = stat_get_int(c, "memory.memsw.limit_in_bytes"); + mem->kmem_used = stat_get_int(c, "memory.kmem.usage_in_bytes"); + mem->kmem_limit = stat_get_int(c, "memory.kmem.limit_in_bytes"); return mem->used > 0 ? 0 : -1; } @@ -424,6 +432,7 @@ static void stats_get(struct lxc_container *c, struct container_stats *ct, struc cg1_get_blk_stats(c, "blkio.throttle.io_serviced", &ct->stats->io_serviced); } + if (total) { total->mem.used += ct->stats->mem.used; total->mem.limit += ct->stats->mem.limit; From 9c552055bb5eaaf01a549056b7d3d0db1803972e Mon Sep 17 00:00:00 2001 From: Alexander Mikhalitsyn Date: Mon, 18 May 2026 15:07:33 +0200 Subject: [PATCH 13/16] templates/lxc-busybox: handle container names with spaces Fixes: #4468 Signed-off-by: Alexander Mikhalitsyn --- templates/lxc-busybox.in | 76 ++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/templates/lxc-busybox.in b/templates/lxc-busybox.in index 04fa96333b..78e4dccbbd 100755 --- a/templates/lxc-busybox.in +++ b/templates/lxc-busybox.in @@ -42,33 +42,33 @@ install_busybox() rootfs="${1}" name="${2}" res=0 - fstree="\ -${rootfs}/selinux \ -${rootfs}/dev \ -${rootfs}/home \ -${rootfs}/root \ -${rootfs}/etc \ -${rootfs}/etc/init.d \ -${rootfs}/bin \ -${rootfs}/usr/bin \ -${rootfs}/sbin \ -${rootfs}/usr/sbin \ -${rootfs}/proc \ -${rootfs}/sys \ -${rootfs}/mnt \ -${rootfs}/tmp \ -${rootfs}/var/log \ -${rootfs}/var/run \ -${rootfs}/dev/pts \ -${rootfs}/lib \ -${rootfs}/usr/lib \ -${rootfs}/lib64 \ -${rootfs}/usr/lib64" - - # shellcheck disable=SC2086 - mkdir -p ${fstree} || return 1 - # shellcheck disable=SC2086 - chmod 755 ${fstree} || return 1 + + for d in \ + selinux \ + dev \ + home \ + root \ + etc \ + etc/init.d \ + bin \ + usr/bin \ + sbin \ + usr/sbin \ + proc \ + sys \ + mnt \ + tmp \ + var/log \ + var/run \ + dev/pts \ + lib \ + usr/lib \ + lib64 \ + usr/lib64 + do + mkdir -p "${rootfs}/$d" || return 1 + chmod 755 "${rootfs}/$d" || return 1 + done # make /tmp accessible to any user (with sticky bit) chmod 1777 "${rootfs}/tmp" || return 1 @@ -107,10 +107,10 @@ EOF # Look for the pathname of "default.script" from the help of udhcpc DEF_SCRIPT=$(${BUSYBOX_EXE} udhcpc --help 2>&1 | egrep -- '-s.*Run PROG' | cut -d'/' -f2- | cut -d')' -f1) DEF_SCRIPT_DIR=$(dirname /${DEF_SCRIPT}) - mkdir -p ${rootfs}/${DEF_SCRIPT_DIR} - chmod 644 ${rootfs}/${DEF_SCRIPT_DIR} || return 1 + mkdir -p "${rootfs}/${DEF_SCRIPT_DIR}" + chmod 644 "${rootfs}/${DEF_SCRIPT_DIR}" || return 1 - cat <> ${rootfs}/${DEF_SCRIPT} + cat <> "${rootfs}/${DEF_SCRIPT}" #!/bin/sh case "\$1" in deconfig) @@ -147,7 +147,7 @@ esac exit 0 EOF - chmod 744 ${rootfs}/${DEF_SCRIPT} + chmod 744 "${rootfs}/${DEF_SCRIPT}" return "${res}" } @@ -193,7 +193,7 @@ grep -q "^lxc.rootfs.path" "${path}/config" 2>/dev/null || echo "lxc.rootfs.path cat <> "${path}/config" lxc.signal.halt = SIGUSR1 lxc.signal.reboot = SIGTERM -lxc.uts.name = "${name}" +lxc.uts.name = ${name} lxc.autodev = 1 lxc.tty.max = 5 lxc.pty.max = 1 @@ -270,12 +270,12 @@ while true do case "$1" in -h|--help) usage && exit 0;; - -n|--name) name=$2; shift 2;; - -p|--path) path=$2; shift 2;; - --rootfs) rootfs=$2; shift 2;; - --mapped-uid) LXC_MAPPED_UID=$2; shift 2;; - --mapped-gid) LXC_MAPPED_GID=$2; shift 2;; - --busybox-path) BUSYBOX_EXE=$2; shift 2;; + -n|--name) name="$2"; shift 2;; + -p|--path) path="$2"; shift 2;; + --rootfs) rootfs="$2"; shift 2;; + --mapped-uid) LXC_MAPPED_UID="$2"; shift 2;; + --mapped-gid) LXC_MAPPED_GID="$2"; shift 2;; + --busybox-path) BUSYBOX_EXE="$2"; shift 2;; --) shift 1; break ;; *) break ;; esac From e4e1fa469228db6d54c4945bbcdbd5abe52bb1ca Mon Sep 17 00:00:00 2001 From: Li Lu <1487442471@qq.com> Date: Tue, 19 May 2026 15:07:33 +0800 Subject: [PATCH 14/16] lxc/caps: fix cap_to_text() memory cleanup Signed-off-by: DreamConnected <1487442471@qq.com> --- src/lxc/caps.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lxc/caps.c b/src/lxc/caps.c index 206488ff43..f6c5770ed3 100644 --- a/src/lxc/caps.c +++ b/src/lxc/caps.c @@ -88,7 +88,7 @@ int lxc_caps_up(void) int lxc_ambient_caps_up(void) { call_cleaner(cap_free) cap_t caps = NULL; - __do_free char *cap_names = NULL; + char *cap_names = NULL; int ret; cap_value_t cap; cap_value_t last_cap = CAP_LAST_CAP; @@ -135,6 +135,7 @@ int lxc_ambient_caps_up(void) return log_warn_errno(0, errno, "Failed to convert capabilities %d", cap); TRACE("Raised %s in inheritable and ambient capability set", cap_names); + cap_free(cap_names); return 0; } From d216033cc6ba395211ea1d46e77823c2445c35d0 Mon Sep 17 00:00:00 2001 From: DreamConnected <1487442471@qq.com> Date: Tue, 19 May 2026 16:00:23 +0800 Subject: [PATCH 15/16] lxc-autostart: fix exit code logic for partial failure Signed-off-by: DreamConnected <1487442471@qq.com> --- src/lxc/tools/lxc_autostart.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lxc/tools/lxc_autostart.c b/src/lxc/tools/lxc_autostart.c index 608398ae08..ad32c0224f 100644 --- a/src/lxc/tools/lxc_autostart.c +++ b/src/lxc/tools/lxc_autostart.c @@ -309,7 +309,7 @@ static int toss_list(struct lxc_list *c_groups_list) int __attribute__((weak, alias("lxc_autostart_main"))) main(int argc, char *argv[]); int lxc_autostart_main(int argc, char *argv[]) { - int count = 0, failed = 0, i = 0, ret = 0; + int count = 0, failed = 0, attempted = 0, i = 0, ret = 0; struct lxc_list *cmd_group; struct lxc_container **containers = NULL; struct lxc_list **c_groups_lists = NULL; @@ -408,6 +408,7 @@ int lxc_autostart_main(int argc, char *argv[]) } /* We have a candidate container to process */ + attempted++; c->want_daemonize(c, 1); if (my_args.shutdown) { @@ -506,7 +507,7 @@ int lxc_autostart_main(int argc, char *argv[]) toss_list(cmd_groups_list); free(containers); - if (failed > 0 && failed == count) + if (failed > 0 && failed == attempted) exit(EXIT_FAILURE); /* Total failure */ else if (failed > 0) exit(2); /* Partial failure */ From 2d5841b4d8365006114c215eef3c4b12fbd9ac8b Mon Sep 17 00:00:00 2001 From: Alexander Mikhalitsyn Date: Thu, 21 May 2026 16:14:52 +0200 Subject: [PATCH 16/16] doc: remove mentions of lxc.console.buffer.logfile This option was removed in 23e0d9af767d ("confile: remove lxc.console.buffer.logfile"). Fixes: #4494 Signed-off-by: Alexander Mikhalitsyn --- doc/lxc.container.conf.sgml.in | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/lxc.container.conf.sgml.in b/doc/lxc.container.conf.sgml.in index ae58e5e3ba..8ba375df9c 100644 --- a/doc/lxc.container.conf.sgml.in +++ b/doc/lxc.container.conf.sgml.in @@ -953,9 +953,7 @@ Note that in contrast to the on-disk ringbuffer logfile this file will keep growing potentially filling up the users disks if not rotated and deleted. This problem can also be avoided by using the - in-memory ringbuffer options - and - . + in-memory ringbuffer option . @@ -974,8 +972,7 @@ Users wishing to prevent the console log file from filling the disk should rotate the logfile and delete it if unneeded. This problem can also be avoided by using the in-memory ringbuffer - options and - . + option .