From c85a67a616217d121d128ecb8e634d7e25a83219 Mon Sep 17 00:00:00 2001 From: Serge Hallyn Date: Sat, 9 May 2026 15:03:08 +0000 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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; }