Skip to content
Snippets Groups Projects
swmod_lxc.c 19.01 KiB
/*
 * swmod_lxc.c: SWMOD deamon
 *
 * Copyright (C) 2020 iopsys Software Solutions AB. All rights reserved.
 *
 * Author: Amin Ben Ramdhane <amin.benramdhane@pivasoftware.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif

#include <stdio.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#include <sys/statvfs.h>
#include <sys/utsname.h>
#include <errno.h>


#include "tools.h"
#include "swmod_uci.h"
#include "swmod_opkg.h"
#include "swmod.h"
#include "swmod_lxc.h"

#define LXC_CONFIG_PATH "/etc/lxc/lxc.conf"
#define LXC_PATH "lxc.lxcpath"

static char *g_lxc_buff;

enum {
	LXC_START,
	LXC_STOP,
	LXC_PAUSE,
	LXC_RESUME,
	__LXC_STATE_MAX
};

enum {
	LXC_SUCCESS,
	LXC_FAILURE,
	LXC_NOT_SUPPORTED,
	LXC_ID_NOT_FOUND,
	LXC_ALREADY_RUNNING,
	LXC_NOT_RUNNING,
	INVALID_REQUEST,
	__LXC_ERROR_MAX
};

static int get_requested_lxc_state(char *state)
{
	if (strcmp(state, "start") == 0) {
		return LXC_START;
	} else if (strcmp(state, "stop") == 0) {
		return LXC_STOP;
	} else if (strcmp(state, "pause") == 0) {
		return LXC_PAUSE;
	} else if (strcmp(state, "resume") == 0) {
		return LXC_RESUME;
	} else {
		return __LXC_STATE_MAX;
	}
}

char ee_set_state_error_msg[__LXC_ERROR_MAX][60] = {
	"",
	"internal failure",
	"LXC not supported",
	"container not found",
	"ExecEnv already running",
	"ExecEnv not running",
	"invalid request"
};

typedef struct lxc_attach_args {
	const char *lxcpath;
	const char *value;
	int action;
} lxc_attach_args;

struct service_state {
	char *name;
	bool state;
};

const char *ee_state_failure_reason(int err)
{
	if (err >= __LXC_ERROR_MAX || err < 0)
		return "unknown";

	return ee_set_state_error_msg[err];
}

const char *get_lxc_path_from_config(void)
{
	return lxc_get_global_config_item(LXC_PATH);
}

bool lxc_is_supported(const char **lxcpath)
{
	if (!file_exists(LXC_CONFIG_PATH))
		return false;

	*lxcpath = get_lxc_path_from_config();

	if (!(*lxcpath) || !file_exists(*lxcpath))
		return false;

	return true;
}

static int lxc_attach_func(struct lxc_container *ct, lxc_attach_exec_t exec_function, lxc_attach_args *command)
{
	int ret;
	FILE *fp;
	pid_t pid;
	char out_file[512];
	const char *lxcpath = NULL;
	size_t buff_len = 0;
	lxc_attach_options_t attach_options = LXC_ATTACH_OPTIONS_DEFAULT;

	g_lxc_buff = NULL;
	if (!lxc_is_supported(&lxcpath))
		return -1;
	const char *state = ct->state(ct);

	if (strcmp(state, "RUNNING") != 0) {
		PRINT_ERR("Container might be frozen");
		return -1;
	}

	ret = ct->attach(ct, exec_function, command, &attach_options, &pid);
	if (ret < 0)
		goto err;

	wait_for_pid(pid);

	snprintf(out_file, 512, "%s/%s/rootfs/swmod_output.log", lxcpath, ct->name);
	fp = fopen(out_file, "r");
	if (fp) {
		fseek(fp, 0L, SEEK_END);
		buff_len = ftell(fp);

		fseek(fp, 0L, SEEK_SET);
		g_lxc_buff = (char *) malloc((buff_len + 1) * sizeof(char));
		if (g_lxc_buff) {
			fread(g_lxc_buff, sizeof(char), buff_len, fp);
			g_lxc_buff[buff_len] = '\0';
		}
		fclose(fp);
	}
	PRINT_DEBUG("== Attach Result len[%d][%s]", buff_len, g_lxc_buff);
err:
	return ret;
}

static void dump_output(const char *str)
{
	FILE *fp;

	if (str == NULL)
		return;

	fp = fopen("/swmod_output.log", "w+");
	if (fp) {
		fwrite(str, sizeof(char), strlen(str), fp);
		fclose(fp);
	}

}

/************************** Execution Environments **************************/
static int lxc_attach_run_env_func(void *args)
{
	char buff[1024];
	struct utsname utsname;
	char vendor[128] = {0}, version[16] = {0};

	if (uname(&utsname) >= 0) {
		swmod_strncpy(vendor, utsname.nodename, 128);
		swmod_strncpy(version, utsname.release, 16);
	}

	struct sysinfo sinfo;
	unsigned long alloc_mem = 0, avail_mem = 0;

	if (sysinfo(&sinfo) == 0) {
		alloc_mem = (sinfo.totalram / 1024);
		avail_mem = (sinfo.freeram / 1024);
	}

	/* lxc_attach_result buffer format */
	/* type=<ENV_TYPE> vendor=<ENV_VENDOR> version=<ENV_VERSION>
	 *  alloc_mem=<ENV_ALLOCATED_MEMORY> avail_mem=<ENV_AVAILABLE_MEMORY> */
	sprintf(buff, "vendor=%s version=%s alloc_mem=%lu avail_mem=%lu",
			vendor, version, alloc_mem, avail_mem);

	dump_output(buff);

	return 0;
}

void populate_lxc_environment(void)
{
	const char *lxcpath = NULL;
	char cmd[1024];

	if (!lxc_is_supported(&lxcpath))
		return;

	struct lxc_container **clist = NULL;
	int i, lxc_nbr;

	lxc_nbr = list_all_containers(lxcpath, NULL, &clist);
	for (i = 0; i < lxc_nbr; i++) {
		struct lxc_container *ct = clist[i];

		bool ct_running = ct->is_running(ct);
		environments[i+1].exists = true;
		swmod_strncpy(environments[i+1].name, ct->name, 32);
		swmod_strncpy(environments[i+1].status, ct_running ? "Up" : "Disabled", 9);
		if (ct_running && ((strcmp(ct->state(ct), "FROZEN") == 0) || strcmp(ct->state(ct), "FREEZING") == 0)) {
				environments[i+1].pause = 1;
		} else {
				environments[i+1].pause = 0;
		}

		swmod_strncpy(environments[i+1].type, "Linux Container", 16);
		snprintf(cmd, 1024, "/etc/swmod/map_du_%s", ct->name);
		create_file(cmd);

		if (!ct_running || environments[i+1].pause) {
			PRINT_INFO("lxc container not running or frozen");
			lxc_container_put(ct);
			continue;
		}

		int ret = lxc_attach_func(ct, lxc_attach_run_env_func, NULL);
		if (ret >= 0 && g_lxc_buff) {
			/* lxc_attach_result buffer format */
			/* type=<ENV_TYPE> vendor=<ENV_VENDOR> version=<ENV_VERSION>
			 *  alloc_mem=<ENV_ALLOCATED_MEMORY> avail_mem=<ENV_AVAILABLE_MEMORY> */
			sscanf(g_lxc_buff, "vendor=%127s version=%15s alloc_mem=%lu avail_mem=%lu",
					environments[i+1].vendor,
					environments[i+1].version,
					&(environments[i+1].allocated_memory),
					&(environments[i+1].available_memory));

			FREE(g_lxc_buff);

			/* get memory space limit in cgroup */
			char buffer[4096] = {0};
			int res = ct->get_cgroup_item(ct, "memory.limit_in_bytes", buffer, sizeof(buffer));
			if (res >= 0) {
				unsigned long long memory = 0;
				if (sscanf(buffer, "%llu", &memory)) {
					memory = memory / 1024; /* converted to KB */
					if (memory < environments[i+1].allocated_memory)
						environments[i+1].allocated_memory = memory;
				}
			}

			/* get free space from lxc info */
			memset(buffer, 0, sizeof(buffer));
			res = ct->get_cgroup_item(ct, "memory.usage_in_bytes", buffer, sizeof(buffer));
			if (res >= 0) {
				unsigned long long used_space = 0;
				if (sscanf(buffer, "%llu", &used_space)) {
					used_space = used_space / 1024; /* converted to KB */
					environments[i+1].available_memory = environments[i+1].allocated_memory - used_space;
				}
			}
		}

		if (ret == -1) {
			swmod_strncpy(environments[i+1].status, "Error", 6);
		}

		struct statvfs dinfo;
		char lxc_root[4096] = {0};
		snprintf(lxc_root, sizeof(lxc_root), "%s/%s/", lxcpath, ct->name);
		if (statvfs(lxc_root, &dinfo) == 0) {
			environments[i+1].allocated_disk_space = (dinfo.f_bsize * dinfo.f_blocks) / 1024;
			environments[i+1].available_disk_space = (dinfo.f_bsize * dinfo.f_bfree) / 1024;
		} else {
			environments[i+1].allocated_disk_space = 0;
			environments[i+1].available_disk_space = 0;
		}

		lxc_container_put(ct);
	}

	if (lxc_nbr > 0)
		FREE(clist);
}

void lxc_service_get_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	FILE *fp;

	fp = fopen("/swmod_output.log", "w+");
	if (fp) {
		char *str;

		str = blobmsg_format_json(msg, true);
		if (str != NULL) {
			size_t len;

			len = strlen(str);
			fwrite(str, sizeof(char), len, fp);
			free(str);
		}
		fclose(fp);
	}
}

static int lxc_attach_pid(void *args)
{
	char buff[1024];
	char cmdline[128] = {0};
	int *pid = (int *)args;
	int vsize = 0;

	get_pid_details(*pid, cmdline, &vsize);
	/* lxc_attach_result buffer format */
	/* type=<ENV_TYPE> vendor=<ENV_VENDOR> version=<ENV_VERSION>
	 *  alloc_mem=<ENV_ALLOCATED_MEMORY> avail_mem=<ENV_AVAILABLE_MEMORY> */
	sprintf(buff, "cmdline=%s vsize=%d", cmdline, vsize);

	dump_output(buff);

	return 0;
}
void get_pid_details_lxc(struct lxc_container *ct, int pid, char *cmdline, int *vsize)
{
	int ret = lxc_attach_func(ct, lxc_attach_pid, (lxc_attach_args *)&pid);
	if (ret >= 0 && g_lxc_buff) {
		sscanf(g_lxc_buff, "cmdline=%31s vsize=%d", cmdline, vsize);
		FREE(g_lxc_buff);
	}
}

static int lxc_attach_eu_list(void *args)
{
	populate_service_list(lxc_service_get_cb);

	return 0;
}

void populate_lxc_eu(void)
{
	const char *lxcpath = NULL;
	struct blob_buf bb;

	if (!lxc_is_supported(&lxcpath))
		return;

	struct lxc_container **clist = NULL;
	int i, lxc_nbr;

	lxc_nbr = list_all_containers(lxcpath, NULL, &clist);
	for (i = 0; i < lxc_nbr; i++) {
		struct lxc_container *ct = clist[i];

		if (!ct->is_running(ct)) {
			lxc_container_put(ct);
			continue;
		}

		int ret = lxc_attach_func(ct, lxc_attach_eu_list, NULL);
		if (ret >= 0 && g_lxc_buff) {
			memset(&bb, 0, sizeof(struct blob_buf));
			blob_buf_init(&bb, 0);
			blobmsg_add_json_from_string(&bb, g_lxc_buff);
			FREE(g_lxc_buff);
			update_eu_from_blob(ct, i + 1, ct->name, bb.head);
			blob_buf_free(&bb);
		}
		lxc_container_put(ct);
	}

	if (lxc_nbr > 0)
		FREE(clist);
}

/************************** Deployment/Execution Unit **************************/
void populate_lxc_du(int eid)
{
	const char *lxcpath = NULL;
	struct lxc_container **clist = NULL;
	int i, lxc_nbr;


	if (!lxc_is_supported(&lxcpath))
		return;

	lxc_nbr = list_all_containers(lxcpath, NULL, &clist);
	PRINT_DEBUG("Number of container %d", lxc_nbr);
	for (i = 0; i < lxc_nbr; i++) {
		char root[512];
		struct lxc_container *ct = clist[i];

		snprintf(root, 512, "%s/%s/rootfs", lxcpath, ct->name);
		PRINT_DEBUG("Reading from container[%s] path[%s]", ct->name, root);
		populate_opkg_deployment_unit(eid + i, root, ct->name);
	}

	if (lxc_nbr > 0)
		FREE(clist);
}

void update_lxc_du_blob(struct blob_buf *bb)
{
	const char *lxcpath = NULL;
	struct lxc_container **clist = NULL;
	int i, lxc_nbr;


	if (!lxc_is_supported(&lxcpath))
		return;

	lxc_nbr = list_all_containers(lxcpath, NULL, &clist);
	for (i = 0; i < lxc_nbr; i++) {
		char map_du[512];
		struct lxc_container *ct = clist[i];
		struct uci_section *ss = NULL;

		snprintf(map_du, 512, "map_du_%s", ct->name);

		swmod_uci_init(SWMOD_PATH);

		swmod_uci_foreach_section(map_du, "deployment", ss) {

			void *t = blobmsg_open_table(bb, "");
			// PRINT_DEBUG("### %s:: %s", map_du, swmod_uci_get_value_by_section(ss, "name"));
			blobmsg_add_string(bb, "name", swmod_uci_get_value_by_section(ss, "name"));
			blobmsg_add_string(bb, "environment", swmod_uci_get_value_by_section(ss, "environment"));
			blobmsg_add_string(bb, "eid", swmod_uci_get_value_by_section(ss, "eid"));
			blobmsg_add_string(bb, "uuid", swmod_uci_get_value_by_section(ss, "uuid"));
			blobmsg_add_string(bb, "duid", swmod_uci_get_value_by_section(ss, "duid"));
			blobmsg_add_string(bb, "url", swmod_uci_get_value_by_section(ss, "url"));
			blobmsg_add_string(bb, "version", swmod_uci_get_value_by_section(ss, "version"));
			blobmsg_add_string(bb, "config", swmod_uci_get_value_by_section(ss, "config"));
			blobmsg_add_string(bb, "description", swmod_uci_get_value_by_section(ss, "description"));
			blobmsg_add_string(bb, "vendor", ""); //TODO

			blobmsg_close_table(bb, t);
		}
		swmod_uci_fini(map_du);
	}

	if (lxc_nbr > 0)
		FREE(clist);
}
/************************** Install/Remove **************************/
static int swmod_lxc_install_package(const char *package_path)
{
	/* lxc_attach_result buffer format */
	/* pkg_name=<PACKAGE_NAME> pkg_version=<PACKAGE_VERSION> */

	char command[1024] = {0};
	char pkg_name[64] = {0};
	char pkg_version[64] = {0};
	FILE *log;
	int err = -1;
	char buff[1024];

	snprintf(command, sizeof(command), "opkg --force-depends --force-maintainer install %s", package_path);

	if ((log = popen(command, "r"))) {
		char line[512] = {0};

		while(fgets(line, sizeof(line), log) != NULL) {

			if (strstr(line, "Installing")) {
				sscanf(line, "Installing %63s (%63[^)] to root...", pkg_name, pkg_version);
				err = 0;
			}
		}
		pclose(log);
	}

	snprintf(buff, 1024, "pkg_name=%s pkg_version=%s", pkg_name, pkg_version);
	dump_output(buff);

	return err;
}

static int swmod_lxc_update_package(const char *package_path)
{
	/* lxc_attach_result buffer format */
	/* pkg_name=<PACKAGE_NAME> pkg_version=<PACKAGE_VERSION> */

	char command[1024] = {0};
	char pkg_name[64] = {0};
	char pkg_new_version[64] = {0};
	FILE *log;
	int err = -1;
	char buff[1024];

	snprintf(command, sizeof(command), "opkg --force-depends --force-maintainer install %s", package_path);

	if ((log = popen(command, "r"))) {
		char line[512] = {0};
		char pkg_old_version[64] = {0};

		while(fgets(line, sizeof(line), log) != NULL) {

			if (strstr(line, "Upgrading")) {
				sscanf(line, "Upgrading %63s on root from %63s to %63s", pkg_name, pkg_old_version, pkg_new_version);
				err = 0;
			}
		}
		pclose(log);
		int len = strlen(pkg_new_version);
		if (len > 3 && pkg_new_version[len - 3] == '.')
			pkg_new_version[len - 3] = '\0';
	}

	snprintf(buff, 1024, "pkg_name=%s pkg_version=%s", pkg_name, pkg_new_version);
	dump_output(buff);

	return err;
}

static int swmod_lxc_remove_package(const char *pname)
{
	int err = 0;
	char buff[1024];

	/* lxc_attach_result buffer format */
	/* pkg_name=<PACKAGE_NAME> pkg_version=<PACKAGE_VERSION> */
	err = swmod_host_system_remove_package(pname);
	snprintf(buff, 1024, "Removing package: done");
	dump_output(buff);
	return err;
}

static int lxc_attach_run_opkg_method_func(void *args)
{
	lxc_attach_args *data = (lxc_attach_args *)args;

	if (!data || *(data->value) == '\0') {
		PRINT_ERR("No data available");
		return -1;
	}

	int err = -1;

	if (data->action == SWMOD_INSTALL)
		err = swmod_lxc_install_package(data->value);
	else if (data->action == SWMOD_UPDATE)
		err = swmod_lxc_update_package(data->value);
	else if (data->action == SWMOD_REMOVE)
		err = swmod_lxc_remove_package(data->value);
	else
		err = -1;

	return err;
}

static int copy_file_to_lxc(const char *host_file, const char *lxc_file)
{
	FILE *source, *target;
	size_t n;
	char buff[1000] = {0};

	source = fopen(host_file, "rb");
	if (!source)
		return -1;

	target = fopen(lxc_file, "wb");
	if (!target) {
		fclose(source);
		return -1;
	}

	while ((n = fread(buff, 1, sizeof(buff), source)) != 0)
		fwrite(buff, 1, n, target);

	fclose(source);
	fclose(target);

	return 0;
}

int swmod_lxc_copy_file_from_host(int cid, const char *package_path, char *lxc_pkg_path, int size)
{
	struct lxc_container **clist = NULL;
	const char *lxcpath = NULL;
	struct lxc_container *ct = NULL;
	int ret = -1;

	if (!package_path || !lxc_pkg_path)
		return ret;

	if (!lxc_is_supported(&lxcpath)) {
		PRINT_ERR("LXC not supported");
		return ret;
	}

	int lxc_nbr = list_all_containers(lxcpath, NULL, &clist);
	if (cid >= lxc_nbr) {
		PRINT_ERR("Container does not exists at cid %d", cid);
		goto end;
	}

	ct = clist[cid];
	if (!ct->is_running(ct)) {
		lxc_container_put(ct);
		goto end;
	}
	snprintf(lxc_pkg_path, size, "%s/%s/rootfs%s", lxcpath, ct->name, package_path);
	if (0 != copy_file_to_lxc(package_path, lxc_pkg_path)) {
		goto end;
	}

	ret = 0;

end:
	if (lxc_nbr > 0)
		FREE(clist);
	return ret;
}

int swmod_lxc_install_update_remove_package(const char *package_path, int cid, int action)
{
	struct lxc_container **clist = NULL;
	const char *lxcpath = NULL;
	int lxc_nbr, err = -1;
	struct lxc_container *ct = NULL;

	if (!lxc_is_supported(&lxcpath)) {
		PRINT_ERR("LXC not supported");
		return -1;
	}

	lxc_nbr = list_all_containers(lxcpath, NULL, &clist);
	if (cid >= lxc_nbr) {
		PRINT_ERR("Container does not exists at cid %d", cid);
		return -1;
	}

	ct = clist[cid];
	if (!ct->is_running(ct)) {
		lxc_container_put(ct);
		goto end;
	}

	PRINT_DEBUG("## Container %s, lxcid %d", ct->name, cid);
	lxc_attach_args lxc_args = (lxc_attach_args){.value = package_path, .action = action};
	err = lxc_attach_func(ct, lxc_attach_run_opkg_method_func, &lxc_args);
	if (err >= 0 && g_lxc_buff) {
		if (action == SWMOD_INSTALL || action == SWMOD_UPDATE) {
			/* lxc_attach_result buffer format */
			/* pkg_name=<PACKAGE_NAME> pkg_version=<PACKAGE_VERSION> */
			sscanf(g_lxc_buff, "pkg_name=%63s pkg_version=%63s",
					package_name, package_version);
		}

		char root[512];
		snprintf(root, 512, "%s/%s/rootfs", lxcpath, ct->name);
		PRINT_DEBUG("Reading from container[%s] path[%s]", ct->name, root);
		populate_opkg_deployment_unit(cid + 2, root, ct->name);

		FREE(g_lxc_buff);
		err = 0;
	}
	PRINT_DEBUG("# Reading i/u/r %d, lxc[%s], err %d", action, ct->name, err);

	lxc_container_put(ct);

end:
	if (lxc_nbr > 0)
		FREE(clist);

	return err;
}

static int lxc_attach_service_state_func(void *args)
{
	struct service_state *st = (struct service_state *)args;

	return ubus_call_service_state(st->name, st->state);
}

int swmod_set_ee_state(int index, char *state)
{
	int ret = LXC_SUCCESS;
	const char *lxcpath = NULL;
	int cid = index - 1;

	if (!lxc_is_supported(&lxcpath)) {
		return LXC_NOT_SUPPORTED;
	}

	struct lxc_container **clist = NULL;
	int lxc_nbr;

	lxc_nbr = list_all_containers(lxcpath, NULL, &clist);

	if (cid >= lxc_nbr) {
		ret = LXC_ID_NOT_FOUND;
		goto end;
	}

	struct lxc_container *ct = clist[cid];

	int req_state = get_requested_lxc_state(state);

	switch (req_state) {
	case LXC_START:
		if (ct->is_running(ct)) {
			ret = LXC_ALREADY_RUNNING;
			break;
		}

		ret = ct->start(ct, 0, NULL) ? LXC_SUCCESS: LXC_FAILURE;
		break;
	case LXC_STOP:
		if (!ct->is_running(ct)) {
			ret = LXC_NOT_RUNNING;
			break;
		}

		if (strcmp(ct->state(ct), "FROZEN") == 0 || strcmp(ct->state(ct), "FREEZING") == 0) {
			/* to stop a frozen container first need to unfreeze it */
			if (!ct->unfreeze(ct)) {
				ret = LXC_FAILURE;
				break;
			}
		}

		ret = ct->stop(ct) ? LXC_SUCCESS : LXC_FAILURE;
		break;
	case LXC_PAUSE:
		if (!ct->is_running(ct)) {
			ret = LXC_NOT_RUNNING;
			break;
		}

		ret = ct->freeze(ct) ? LXC_SUCCESS : LXC_FAILURE;
		break;
	case LXC_RESUME:
		if (!ct->is_running(ct)) {
			ret = LXC_NOT_RUNNING;
			break;
		}

		ret = ct->unfreeze(ct) ? LXC_SUCCESS : LXC_FAILURE;
		break;
	default:
		ret = INVALID_REQUEST;
	}

	lxc_container_put(ct);

end:
	if (lxc_nbr > 0)
		FREE(clist);

	return ret;
}

int swmod_set_lxc_service_state(int index, char *name, bool state)
{
	int ret = -1;
	const char *lxcpath = NULL;
	int cid = index - 1;

	if (!lxc_is_supported(&lxcpath)) {
		return ret;
	}

	struct lxc_container **clist = NULL;
	int lxc_nbr;

	lxc_nbr = list_all_containers(lxcpath, NULL, &clist);

	if (cid >= lxc_nbr) {
		goto end;
	}

	struct lxc_container *ct = clist[cid];

	if (!ct->is_running(ct)) {
		PRINT_INFO("lxc container not running");
		lxc_container_put(ct);
		goto end;
	}

	struct service_state st;
	st.name = name;
	st.state = state;
	ret = lxc_attach_func(ct, lxc_attach_service_state_func, (lxc_attach_args *)&st);
	lxc_container_put(ct);

end:
	if (lxc_nbr > 0)
		FREE(clist);

	return ret;
}