| /* |
| * 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; |
| } |