blob: 6c006784e11a30bbde4756d97841866d9fa8a08c [file] [log] [blame] [raw]
/*
* Copyright (C) 2000-2009, Parallels, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <limits.h>
#include "types.h"
#include "ub.h"
#include "env.h"
#include "vzctl_param.h"
#include "vzerror.h"
#include "logger.h"
#include "vzsyscalls.h"
#include "util.h"
static struct ubname2id {
char *name;
unsigned int id;
} ubname2id[] = {
{"KMEMSIZE", PARAM_KMEMSIZE},
{"LOCKEDPAGES", PARAM_LOCKEDPAGES},
{"PRIVVMPAGES", PARAM_PRIVVMPAGES},
{"SHMPAGES", PARAM_SHMPAGES},
{"NUMPROC", PARAM_NUMPROC},
{"PHYSPAGES", PARAM_PHYSPAGES},
{"VMGUARPAGES", PARAM_VMGUARPAGES},
{"OOMGUARPAGES",PARAM_OOMGUARPAGES},
{"NUMTCPSOCK", PARAM_NUMTCPSOCK},
{"NUMFLOCK", PARAM_NUMFLOCK},
{"NUMPTY", PARAM_NUMPTY},
{"NUMSIGINFO", PARAM_NUMSIGINFO},
{"TCPSNDBUF", PARAM_TCPSNDBUF},
{"TCPRCVBUF", PARAM_TCPRCVBUF},
{"OTHERSOCKBUF",PARAM_OTHERSOCKBUF},
{"DGRAMRCVBUF", PARAM_DGRAMRCVBUF},
{"NUMOTHERSOCK",PARAM_NUMOTHERSOCK},
{"NUMFILE", PARAM_NUMFILE},
{"DCACHESIZE", PARAM_DCACHESIZE},
{"NUMIPTENT", PARAM_NUMIPTENT},
{"AVNUMPROC", PARAM_AVNUMPROC},
{"SWAPPAGES", PARAM_SWAPPAGES},
{NULL, 0},
};
/** Check that all required UBC parameters are specified.
*
* @param ub UBC parameters.
* @return 0 on success.
*/
int check_ub(vps_handler *h, ub_param *ub)
{
int ret = 0;
#define CHECK_UB(name) \
if (ub->name == NULL) { \
logger(-1, 0, "Error: required UB parameter " #name " not set");\
ret = VZ_NOTENOUGHUBCPARAMS; \
}
if (is_vswap_config(ub))
{
CHECK_UB(physpages);
CHECK_UB(swappages);
if (is_vz_kernel(h) && !is_vswap_mode()) {
logger(-1, 0, "Error: detected vswap CT config but "
"kernel does not support vswap");
logger(-1, 0, "This means either old kernel "
"or bad config (physpages NOT set "
"to 0:unlimited)");
ret = VZ_BAD_KERNEL;
}
return ret;
}
/* else
* non-vswap case, require all params except swappages */
CHECK_UB(kmemsize)
CHECK_UB(lockedpages)
CHECK_UB(privvmpages)
CHECK_UB(shmpages)
CHECK_UB(numproc)
CHECK_UB(physpages)
CHECK_UB(vmguarpages)
CHECK_UB(oomguarpages)
CHECK_UB(numtcpsock)
CHECK_UB(numflock)
CHECK_UB(numpty)
CHECK_UB(numsiginfo)
CHECK_UB(tcpsndbuf)
CHECK_UB(tcprcvbuf)
CHECK_UB(othersockbuf)
CHECK_UB(dgramrcvbuf)
CHECK_UB(numothersock)
CHECK_UB(numfile)
CHECK_UB(dcachesize)
CHECK_UB(numiptent)
#undef CHECK_UB
return ret;
}
inline static int is_ub_empty(ub_param *ub)
{
#define CHECK_UB(name) if (ub->name != NULL) return 0;
FOR_ALL_UBC(CHECK_UB)
#undef CHECK_UB
return 1;
}
int get_ub_resid(char *name)
{
int i;
for (i = 0; ubname2id[i].name != NULL; i++)
if (!strcasecmp(name, ubname2id[i].name))
return ubname2id[i].id;
return -1;
}
#ifdef VZ_KERNEL_SUPPORTED
static inline int setublimit(uid_t uid, unsigned long resource,
const unsigned long *rlim)
{
return syscall(__NR_setublimit, uid, resource, rlim);
}
static const char *get_ub_name(unsigned int res_id)
{
int i;
for (i = 0; ubname2id[i].name != NULL; i++)
if (ubname2id[i].id == res_id)
return ubname2id[i].name;
return NULL;
}
int set_ublimit(vps_handler *h, envid_t veid, ub_param *ub)
{
const unsigned long *res;
#define SET_UB_LIMIT(name, id) \
res = ub->name; \
if (res != NULL) { \
if (setublimit(veid, id, res)) { \
logger(-1, errno, "setublimit %s %lu:%lu failed", \
get_ub_name(id), res[0], res[1]); \
return VZ_SETUBC_ERROR; \
} \
}
SET_UB_LIMIT(kmemsize, UB_KMEMSIZE)
SET_UB_LIMIT(lockedpages, UB_LOCKEDPAGES)
SET_UB_LIMIT(privvmpages, UB_PRIVVMPAGES)
SET_UB_LIMIT(shmpages, UB_SHMPAGES)
SET_UB_LIMIT(numproc, UB_NUMPROC)
SET_UB_LIMIT(physpages, UB_PHYSPAGES)
SET_UB_LIMIT(vmguarpages, UB_VMGUARPAGES)
SET_UB_LIMIT(oomguarpages, UB_OOMGUARPAGES)
SET_UB_LIMIT(numtcpsock, UB_NUMTCPSOCK)
SET_UB_LIMIT(numflock, UB_NUMFLOCK)
SET_UB_LIMIT(numpty, UB_NUMPTY)
SET_UB_LIMIT(numsiginfo, UB_NUMSIGINFO)
SET_UB_LIMIT(tcpsndbuf, UB_TCPSNDBUF )
SET_UB_LIMIT(tcprcvbuf, UB_TCPRCVBUF)
SET_UB_LIMIT(othersockbuf, UB_OTHERSOCKBUF)
SET_UB_LIMIT(dgramrcvbuf, UB_DGRAMRCVBUF)
SET_UB_LIMIT(numothersock, UB_NUMOTHERSOCK)
SET_UB_LIMIT(numfile, UB_NUMFILE)
SET_UB_LIMIT(dcachesize, UB_DCACHESIZE)
SET_UB_LIMIT(numiptent, UB_IPTENTRIES)
if (ub->swappages &&
setublimit(veid, UB_SWAPPAGES, ub->swappages) == -1)
{
if (errno == EINVAL) {
logger(-1, ENOSYS, "failed to set swappages");
} else {
logger(-1, errno, "failed to set swappages");
return VZ_SETUBC_ERROR;
}
}
#undef SET_UB_LIMIT
return 0;
}
#endif
/** Apply UBC resources.
*
* @param h CT handler.
* @param veid CT ID.
* @param ubc UBC parameters
* @return 0 on success
*/
int vps_set_ublimit(vps_handler *h, envid_t veid, ub_param *ub)
{
int ret;
if (is_ub_empty(ub))
return 0;
if (!vps_is_run(h, veid)) {
logger(-1, 0, "Unable to apply UBC parameters: "
"container is not running");
return VZ_VE_NOT_RUNNING;
}
if ((ret = h->setlimits(h, veid, ub)))
return ret;
logger(0, 0, "UB limits were set successfully");
return 0;
}
void free_ub_param(ub_param *ub)
{
#define FREE_P(x) free(ub->x); ub->x = NULL;
if (ub == NULL)
return;
FOR_ALL_UBC(FREE_P)
FREE_P(vm_overcommit)
#undef FREE_P
}
void add_ub_limit(struct ub_struct *ub, int res_id, unsigned long *limit)
{
#define ADD_UB_PARAM(name, id) \
if (res_id == id) { \
ub->name = limit; \
return; \
}
ADD_UB_PARAM(kmemsize, PARAM_KMEMSIZE)
ADD_UB_PARAM(lockedpages, PARAM_LOCKEDPAGES)
ADD_UB_PARAM(privvmpages, PARAM_PRIVVMPAGES)
ADD_UB_PARAM(shmpages, PARAM_SHMPAGES)
ADD_UB_PARAM(numproc, PARAM_NUMPROC)
ADD_UB_PARAM(physpages, PARAM_PHYSPAGES)
ADD_UB_PARAM(vmguarpages, PARAM_VMGUARPAGES)
ADD_UB_PARAM(oomguarpages, PARAM_OOMGUARPAGES)
ADD_UB_PARAM(numtcpsock, PARAM_NUMTCPSOCK)
ADD_UB_PARAM(numflock, PARAM_NUMFLOCK)
ADD_UB_PARAM(numpty, PARAM_NUMPTY)
ADD_UB_PARAM(numsiginfo, PARAM_NUMSIGINFO)
ADD_UB_PARAM(tcpsndbuf, PARAM_TCPSNDBUF)
ADD_UB_PARAM(tcprcvbuf, PARAM_TCPRCVBUF)
ADD_UB_PARAM(othersockbuf, PARAM_OTHERSOCKBUF)
ADD_UB_PARAM(dgramrcvbuf, PARAM_DGRAMRCVBUF)
ADD_UB_PARAM(numothersock, PARAM_NUMOTHERSOCK)
ADD_UB_PARAM(numfile, PARAM_NUMFILE)
ADD_UB_PARAM(dcachesize, PARAM_DCACHESIZE)
ADD_UB_PARAM(numiptent, PARAM_NUMIPTENT)
ADD_UB_PARAM(avnumproc, PARAM_AVNUMPROC)
ADD_UB_PARAM(swappages, PARAM_SWAPPAGES)
#undef ADD_UB_PARAM
}
/** Add UBC resource in ub_res format to UBC struct
*
* @param ub UBC parameters.
* @param res UBC resource in ub_res format.
* @return 0 on success.
*/
int add_ub_param(ub_param *ub, ub_res *res)
{
unsigned long *limit;
limit = malloc(sizeof(*limit) * 2);
if (limit == NULL)
return ERR_NOMEM;
limit[0] = res->limit[0];
limit[1] = res->limit[1];
add_ub_limit(ub, res->res_id, limit);
return 0;
}
/** Set secondary beancounters in vswap mode,
* if they were not set explicitly,
* based on ram, swap and overcommit.
*
* @param cfg UBC parameters from CT config
* @param cmd UBC parameters from command line
*/
int fill_vswap_ub(ub_param *cfg, ub_param *cmd)
{
float ovc = 0;
unsigned long ram, swap;
#define SET(param, bar, lim) do { \
if (!cmd->param && !cfg->param) { \
cmd->param = vz_malloc(sizeof(unsigned long)*2); \
if (!cmd->param) \
goto enomem; \
cmd->param[0] = (bar); \
cmd->param[1] = (lim); \
} } while (0)
if (!is_vswap_config(cfg) && !is_vswap_config(cmd))
return 0;
ram = (cmd->physpages ? : cfg->physpages)[1];
if (cmd->swappages)
swap = cmd->swappages[1];
else if (cfg->swappages)
swap = cfg->swappages[1];
else {/* swap not set, but required */
logger(-1, 0, "Error: required UB parameter (swap) not set");
return VZ_NOTENOUGHUBCPARAMS;
}
if (cmd->vm_overcommit)
ovc = *cmd->vm_overcommit;
else if (cfg->vm_overcommit)
ovc = *cfg->vm_overcommit;
SET(lockedpages, ram, ram);
SET(oomguarpages, ram, LONG_MAX);
SET(vmguarpages, ram + swap, LONG_MAX);
if (ovc)
SET(privvmpages, (ram + swap) * ovc, (ram + swap) * ovc);
else
SET(privvmpages, LONG_MAX, LONG_MAX);
#undef SET
return 0;
enomem:
return VZ_RESOURCE_ERROR;
}
/** Read UBC resources current usage from /proc/user_beancounters
*
* @param veid CT ID.
* @param ub UBC parameters.
* @return 0 on success.
*/
int vps_read_ubc(envid_t veid, ub_param *ub)
{
FILE *fd;
char str[STR_SIZE];
char name[64];
const char *fmt = NULL; /* make gcc happy */
int ret, found;
envid_t id;
unsigned long held, maxheld, barrier, limit;
ub_res res;
fd = fopen(PROCUBC, "r");
if (fd == NULL) {
logger(-1, errno, "Unable to open " PROCUBC);
return -1;
}
found = 0;
while (fgets(str, sizeof(str), fd)) {
if ((ret = sscanf(str, "%d:", &id)) == 1) {
if (id == veid) {
fmt = "%*lu:%s%lu%lu%lu%lu";
found = 1;
} else {
if (found)
break;
}
} else {
fmt = "%s%lu%lu%lu%lu";
}
if (!found)
continue;
if ((ret = sscanf(str, fmt,
name, &held, &maxheld, &barrier, &limit)) != 5)
{
continue;
}
if ((res.res_id = get_ub_resid(name)) >= 0) {
res.limit[0] = held;
res.limit[1] = held;
add_ub_param(ub, &res);
}
}
fclose(fd);
return !found;
}
/* If you want to modify this function, don't forget print_vswap() */
int is_vswap_config(const ub_param *param)
{
/* Dirty hack: treat INT_MAX (i.e. 32 bit LONG_MAX) as unlimited.
* Required for old CTs migrated from 32 bit to 64 bit host.
*/
return (param != NULL) &&
(param->physpages != NULL) &&
(param->physpages[1] != LONG_MAX) &&
(param->physpages[1] != INT_MAX);
}