-
Suvendhu Hansa authoredSuvendhu Hansa authored
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;
}