blob: d9c4c177988b48e41a621576db38c59d686ed6e3 [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
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <limits.h>
#include <dirent.h>
#include "util.h"
#include "logger.h"
#include "fs.h"
#ifndef NR_OPEN
#define NR_OPEN 1024
#endif
static const char *unescapestr(char *src)
{
char *p1, *p2;
int fl;
if (src == NULL)
return NULL;
p2 = src;
p1 = p2;
fl = 0;
while (*p2) {
if (*p2 == '\\' && !fl) {
fl = 1;
p2++;
} else {
*p1 = *p2;
p1++; p2++;
fl = 0;
}
}
*p1 = 0;
return src;
}
char *parse_line(char *str, char *ltoken, int lsz, char **err)
{
char *sp = str;
char *ep, *p, *ret;
int len;
*err = NULL;
unescapestr(str);
while (*sp && isspace(*sp)) sp++;
if (!*sp || *sp == '#')
return NULL;
ep = sp + strlen(sp) - 1;
while (isspace(*ep) && ep >= sp) *ep-- = '\0';
if (!(p = strchr(sp, '='))) {
*err = "'=' not found";
return NULL;
}
len = p - sp;
if (len >= lsz) {
*err = "too long value";
return NULL;
}
strncpy(ltoken, sp, len);
ltoken[len] = 0;
if (*(++p) != '"')
return p;
/* Quoted argument requires some additional processing */
ret = ++p; /* skip opening quote */
/* Find the matching closing quote */
if (!(p = strrchr(p, '"'))) {
*err="unmatched quotes";
return NULL;
}
*p = '\0';
return ret;
}
/*
1 - exist
0 - does't exist
-1 - error
*/
int stat_file(const char *file)
{
struct stat st;
if (stat(file, &st)) {
if (errno != ENOENT)
return -1;
return 0;
}
return 1;
}
/** Check if a directory is empty
* Returns:
* 1 - empty or nonexistent
* 0 - not empty
* -1 - error
*/
int dir_empty(const char *dir)
{
DIR *dp;
struct dirent *ep;
int ret = 1;
dp = opendir(dir);
if (dp == NULL) {
if (errno == ENOENT)
return 1;
logger(-1, errno, "Can't opendir %s", dir);
return -1;
}
while ((ep = readdir(dp))) {
if (!strcmp(ep->d_name, "."))
continue;
if (!strcmp(ep->d_name, ".."))
continue;
/* Not empty */
ret = 0;
break;
}
closedir(dp);
return ret;
}
int make_dir_mode(const char *path, int full, int mode)
{
char buf[4096];
const char *ps, *p;
int len;
if (path == NULL)
return 0;
ps = path + 1;
while ((p = strchr(ps, '/'))) {
len = p - path + 1;
snprintf(buf, len, "%s", path);
ps = p + 1;
if (!stat_file(buf)) {
if (mkdir(buf, mode)) {
logger(-1, errno, "Can't create directory %s",
buf);
return 1;
}
}
}
if (!full)
return 0;
if (!stat_file(path)) {
if (mkdir(path, mode)) {
logger(-1, errno, "Can't create directory %s", path);
return 1;
}
}
return 0;
}
int make_dir(const char *path, int full)
{
return make_dir_mode(path, full, 0755);
}
char *get_fs_root(const char *dir)
{
struct stat st;
dev_t id;
size_t len;
const char *p, *prev;
char tmp[PATH_MAX];
if (stat(dir, &st) < 0)
return NULL;
id = st.st_dev;
len = strlen(dir);
if (len > sizeof(tmp) - 1) {
errno = ERANGE;
return NULL;
}
p = dir + len;
prev = p;
while (p > dir) {
while (p > dir && *p == '/') p--;
while (p > dir && *p != '/') p--;
if (p <= dir)
break;
len = p - dir + 1;
strncpy(tmp, dir, len);
tmp[len] = 0;
if (stat(tmp, &st) < 0)
return NULL;
if (id != st.st_dev)
break;
prev = p;
}
len = prev - dir;
if (len) {
strncpy(tmp, dir, len);
tmp[len] = 0;
return strdup(tmp);
}
return NULL;
}
int parse_int(const char *str, int *val)
{
char *tail;
long res;
res = strtol(str, &tail, 10);
if (*tail != '\0' || res < INT_MIN || res > INT_MAX)
return 1;
*val = (int)res;
return 0;
}
int parse_ul(const char *str, unsigned long *val)
{
char *tail;
unsigned long res;
if (!strcmp(str, "unlimited")) {
*val = LONG_MAX;
return 0;
}
res = strtoul(str, &tail, 10);
if (*tail != '\0' || res > LONG_MAX)
return ERR_INVAL;
*val = res;
return 0;
}
int check_var(const void *val, const char *message)
{
if (val != NULL)
return 0;
logger(-1, 0, "%s", message);
return 1;
}
int cp_file(char *dst, char *src)
{
int fd_src, fd_dst, ret = 0;
struct stat st;
char buf[4096];
if (stat(src, &st) < 0) {
logger(-1, errno, "Unable to stat %s", src);
return -1;
}
if ((fd_src = open(src, O_RDONLY)) < 0) {
logger(-1, errno, "Unable to open %s", src);
return -1;
}
if ((fd_dst = open(dst, O_CREAT|O_TRUNC|O_RDWR, st.st_mode)) < 0) {
logger(-1, errno, "Unable to open %s", dst);
close(fd_src);
return -1;
}
while(1) {
ret = read(fd_src, buf, sizeof(buf));
if (!ret)
break;
else if (ret < 0) {
logger(-1, errno, "Unable to read from %s", src);
ret = -1;
break;
}
if (write(fd_dst, buf, ret) < 0) {
logger(-1, errno, "Unable to write to %s", dst);
ret = -1;
break;
}
}
close(fd_src);
close(fd_dst);
return ret;
}
void get_vps_conf_path(envid_t veid, char *buf, int len)
{
snprintf(buf, len, VPS_CONF_DIR "%d.conf", veid);
}
char *arg2str(char **arg)
{
char **p;
char *str, *sp;
int len = 0;
if (arg == NULL)
return NULL;
p = arg;
while (*p)
len += strlen(*p++) + 1;
if ((str = (char *)malloc(len + 1)) == NULL)
return NULL;
p = arg;
sp = str;
while (*p)
sp += sprintf(sp, "%s ", *p++);
return str;
}
void free_arg(char **arg)
{
while (*arg) free(*arg++);
}
inline unsigned long max_ul(unsigned long val1, unsigned long val2)
{
return (val1 > val2) ? val1 : val2;
}
inline unsigned long min_ul(unsigned long val1, unsigned long val2)
{
return (val1 < val2) ? val1 : val2;
}
int yesno2id(const char *str)
{
if (!strcmp(str, "yes"))
return YES;
else if (!strcmp(str, "no"))
return NO;
return -1;
}
int get_addr_family(const char *addr)
{
if (strchr(addr, ':'))
return AF_INET6;
else
return AF_INET;
}
int get_netaddr(const char *ip_str, void *ip)
{
int family;
family = get_addr_family(ip_str);
if (inet_pton(family, ip_str, ip) <= 0)
return -1;
return family;
}
static inline int max_netmask(int family)
{
if (family == AF_INET)
return 32;
else if (family == AF_INET6)
return 128;
else
return -1;
}
/* Check and "canonicalize" an IP address with optional netmask
* (in CIDR notation). Basically */
char *canon_ip(const char *str)
{
const char *ipstr, *maskstr;
int mask, family;
unsigned int ip[4];
static char dst[INET6_ADDRSTRLEN + sizeof("/128")];
maskstr = strchr(str, '/');
if (maskstr) {
ipstr = strndupa(str, maskstr - str);
maskstr++;
}
else {
ipstr = str;
}
family = get_netaddr(ipstr, ip);
if (family < 0)
return NULL;
if ((inet_ntop(family, ip, dst, sizeof(dst))) == NULL)
return NULL;
if (maskstr == NULL)
goto out;
/* Parse netmask */
if (parse_int(maskstr, &mask) != 0)
return NULL;
if (mask > max_netmask(family) || mask < 0)
return NULL;
sprintf(dst + strlen(dst), "/%d", mask);
out:
return dst;
}
char *subst_VEID(envid_t veid, char *src)
{
char *srcp;
char str[STR_SIZE];
char *sp, *se;
int r;
unsigned int len, veidlen;
if (src == NULL)
return NULL;
/* Skip end '/' */
se = src + strlen(src) - 1;
while (se != str && *se == '/') {
*se = 0;
se--;
}
if ((srcp = strstr(src, "$VEID")))
veidlen = sizeof("$VEID") - 1;
else if ((srcp = strstr(src, "${VEID}")))
veidlen = sizeof("${VEID}") - 1;
else
return strdup(src);
sp = str;
se = str + sizeof(str);
len = srcp - src; /* Length of src before $VEID */
if (len > sizeof(str))
return NULL;
memcpy(str, src, len);
sp += len;
r = snprintf(sp, se - sp, "%d", veid);
sp += r;
if ((r < 0) || (sp >= se))
return NULL;
if (*srcp) {
r = snprintf(sp, se - sp, "%s", srcp + veidlen);
sp += r;
if ((r < 0) || (sp >= se))
return NULL;
}
return strdup(str);
}
int get_pagesize()
{
long pagesize;
if ((pagesize = sysconf(_SC_PAGESIZE)) == -1) {
logger(-1, errno, "Unable to get page size");
return -1;
}
return pagesize;
}
int get_mem(unsigned long long *mem)
{
long pages, pagesize;
if ((pages = sysconf(_SC_PHYS_PAGES)) == -1) {
logger(-1, errno, "Unable to get total phys pages");
return -1;
}
if ((pagesize = get_pagesize()) < 0)
return -1;
*mem = (unsigned long long) pages * pagesize;
return 0;
}
int get_thrmax(int *thrmax)
{
FILE *fd;
char str[128];
if (thrmax == NULL)
return 1;
if ((fd = fopen(PROCTHR, "r")) == NULL) {
logger(-1, errno, "Unable to open " PROCTHR);
return 1;
}
if (fgets(str, sizeof(str), fd) == NULL) {
fclose(fd);
return 1;
}
fclose(fd);
if (sscanf(str, "%du", thrmax) != 1)
return 1;
return 0;
}
int get_swap(unsigned long long *swap)
{
FILE *fd;
char str[128];
if ((fd = fopen(PROCMEM, "r")) == NULL) {
logger(-1, errno, "Cannot open " PROCMEM);
return -1;
}
while (fgets(str, sizeof(str), fd)) {
if (sscanf(str, "SwapTotal: %llu", swap) == 1) {
*swap *= 1024;
fclose(fd);
return 0;
}
}
logger(-1, errno, "Swap: is not found in " PROCMEM );
fclose(fd);
return -1;
}
int get_num_cpu()
{
FILE *fd;
char str[128];
int ncpu = 0;
if ((fd = fopen("/proc/cpuinfo", "r")) == NULL) {
logger(-1, errno, "Cannot open /proc/cpuinfo");
return 1;
}
while (fgets(str, sizeof(str), fd)) {
if (!strncmp(str, "processor", 9))
ncpu++;
}
fclose(fd);
return !ncpu ? 1 : ncpu;
}
int get_lowmem(unsigned long long *mem)
{
FILE *fd;
char str[128];
if ((fd = fopen(PROCMEM, "r")) == NULL) {
logger(-1, errno, "Cannot open " PROCMEM);
return -1;
}
*mem = 0;
while (fgets(str, sizeof(str), fd)) {
if (sscanf(str, "LowTotal: %llu", mem) == 1)
break;
/* Use MemTotal in case LowTotal not found */
sscanf(str, "MemTotal: %llu", mem);
}
fclose(fd);
if (*mem == 0) {
fprintf(stderr, "Neither LowTotal nor MemTotal found in the "
PROCMEM "\n");
return -1;
}
*mem *= 1024;
return 0;
}
int get_dump_file(unsigned veid, const char *dumpdir, char *buf, int size)
{
return snprintf(buf, size, "%s/" DEF_DUMPFILE,
dumpdir != NULL ? dumpdir : DEF_DUMPDIR, veid);
}
int set_not_blk(int fd)
{
int oldfl, ret;
if ((oldfl = fcntl(fd, F_GETFL)) == -1)
return -1;
oldfl |= O_NONBLOCK;
ret = fcntl(fd, F_SETFL, oldfl);
return ret;
}
/** Close all fd.
* @param close_std flag for closing the [0-2] fds
* @param ... list of fds are skiped, (-1 is the end mark)
*/
void close_fds(int close_std, ...)
{
int fd, max;
unsigned int i;
va_list ap;
int skip_fds[255];
max = sysconf(_SC_OPEN_MAX);
if (max < NR_OPEN)
max = NR_OPEN;
if (close_std) {
fd = open("/dev/null", O_RDWR);
if (fd != -1) {
dup2(fd, 0); dup2(fd, 1); dup2(fd, 2);
} else {
close(0); close(1); close(2);
}
}
/* build aray of skiped fds */
va_start(ap, close_std);
skip_fds[0] = -1;
for (i = 0; i < ARRAY_SIZE(skip_fds); i++) {
fd = va_arg(ap, int);
skip_fds[i] = fd;
if (fd == -1)
break;
}
va_end(ap);
for (fd = 3; fd < max; fd++) {
for (i = 0; skip_fds[i] != fd && skip_fds[i] != -1; i++);
if (skip_fds[i] == fd)
continue;
close(fd);
}
}
static void __move_config(int veid, int action, const char *prefix)
{
char conf[PATH_LEN];
char newconf[PATH_LEN];
snprintf(conf, sizeof(conf), VPS_CONF_DIR "%d.%s", veid, prefix);
snprintf(newconf, sizeof(newconf), "%s." DESTR_PREFIX, conf);
action == BACKUP ? rename(conf, newconf) : unlink(newconf);
}
/* Renames or removes CT config and various CT scripts.
*/
int move_config(int veid, int action)
{
__move_config(veid, action, "conf");
__move_config(veid, action, MOUNT_PREFIX);
__move_config(veid, action, UMOUNT_PREFIX);
__move_config(veid, action, PRE_MOUNT_PREFIX);
__move_config(veid, action, POST_UMOUNT_PREFIX);
__move_config(veid, action, START_PREFIX);
__move_config(veid, action, STOP_PREFIX);
return 0;
}
void remove_names(envid_t veid)
{
char buf[STR_SIZE];
char content[STR_SIZE];
struct stat st;
struct dirent *ep;
DIR *dp;
char *p;
int r;
envid_t id;
if (!(dp = opendir(VENAME_DIR)))
return;
while ((ep = readdir(dp))) {
snprintf(buf, sizeof(buf), VENAME_DIR "/%s", ep->d_name);
if (lstat(buf, &st))
continue;
if (!S_ISLNK(st.st_mode))
continue;
r = readlink(buf, content, sizeof(content) - 1);
if (r < 0)
continue;
content[r] = 0;
if ((p = strrchr(content, '/')) != NULL)
p++;
if (sscanf(p, "%d.conf", &id) == 1 && veid == id)
unlink(buf);
}
closedir(dp);
}
size_t vz_strlcat(char *dst, const char *src, size_t count)
{
size_t dsize = strlen(dst);
size_t len = strlen(src);
size_t res = dsize + len;
if (dsize >= count)
return dsize;
dst += dsize;
count -= dsize;
if (len >= count)
len = count - 1;
memcpy(dst, src, len);
dst[len] = 0;
return res;
}
static int envid_sort_fn(const void *val1, const void *val2)
{
const envid_t *r1 = (const envid_t *)val1;
const envid_t *r2 = (const envid_t *)val2;
return (*r1 - *r2);
}
/** Returns a sorted array of all running CTs.
* Caller needs to free() it after use
*/
int get_running_ve_list(envid_t **ves)
{
FILE *fp;
int res;
envid_t veid;
int venum = 0;
int ves_size = 256;
*ves = malloc(ves_size * sizeof(envid_t));
if (*ves == NULL)
return -ENOMEM;
if ((fp = fopen(PROCVEINFO, "r")) == NULL) {
return -errno;
}
while (!feof(fp)) {
res = fscanf(fp, "%d %*[^\n]", &veid);
if (res != 1 || !veid)
continue;
if (venum >= ves_size)
ves_size *= 2;
*ves = realloc(*ves, ves_size * sizeof(envid_t));
if (*ves == NULL)
{
venum=-ENOMEM;
goto out;
}
(*ves)[venum++] = veid;
}
qsort(*ves, venum, sizeof(envid_t), envid_sort_fn);
out:
fclose(fp);
return venum;
}
/* Searches for ve in velist */
int ve_in_list(envid_t *list, int size, envid_t ve)
{
return bsearch(&ve, list, size, sizeof(envid_t),
envid_sort_fn) != NULL;
}
const char* ubcstr(unsigned long bar, unsigned long lim)
{
static char str[64];
char *p = str;
char *e = p + sizeof(str) - 1;
#define PRINT_UBC(val) \
if (val == LONG_MAX) \
p += snprintf(p, e - p, "unlimited"); \
else \
p += snprintf(p, e - p, "%lu", val)
PRINT_UBC(bar);
if (bar == lim)
return str;
*p++=':';
PRINT_UBC(lim);
return str;
}
int is_vswap_mode(void)
{
return (access("/proc/vz/vswap", F_OK) == 0);
}
#define UUID_LEN 36
/* Check for valid GUID, add curly brackets if necessary:
* fbcdf284-5345-416b-a589-7b5fcaa87673 ->
* {fbcdf284-5345-416b-a589-7b5fcaa87673}
*/
int vzctl_get_normalized_guid(const char *str, char *buf, int len)
{
int i;
const char *in;
char *out;
if (len < UUID_LEN + 3)
return -1;
in = (str[0] == '{') ? str + 1 : str;
buf[0] = '{';
out = buf + 1;
for (i = 0; i < UUID_LEN; i++) {
if (in[i] == '\0')
break;
if ((i == 8) || (i == 13) || (i == 18) || (i == 23)) {
if (in[i] != '-' )
break;
} else if (!isxdigit(in[i])) {
break;
}
out[i] = in[i];
}
if (i < UUID_LEN)
return 1;
if (in[UUID_LEN] != '\0' &&
(in[UUID_LEN] != '}' || in[UUID_LEN + 1] != '\0'))
return 1;
out[UUID_LEN] = '}';
out[UUID_LEN+1] = '\0';
return 0;
}