| /* |
| * 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 <stdlib.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <netinet/in.h> |
| #include <linux/vzcalluser.h> |
| #ifdef __GLIBC__ |
| #include <sys/sysmacros.h> |
| #else |
| #include <sys/mkdev.h> |
| #endif |
| #include "vzerror.h" |
| #include "logger.h" |
| #include "res.h" |
| #include "exec.h" |
| #include "dist.h" |
| #include "util.h" |
| #include "env.h" |
| #include "vps_configure.h" |
| #include "list.h" |
| #include "image.h" |
| |
| |
| static struct vps_state{ |
| char *name; |
| int id; |
| } vps_states[] = { |
| {"starting", STATE_STARTING}, |
| {"running", STATE_RUNNING}, |
| {"stopping", STATE_STOPPING}, |
| {"restoring", STATE_RESTORING}, |
| {"checkpointing", STATE_CHECKPOINTING}, |
| }; |
| |
| const char *state2str(int state) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(vps_states); i++) |
| if (vps_states[i].id == state) |
| return vps_states[i].name; |
| |
| return NULL; |
| } |
| |
| static const char *get_local_ip_address(vps_param *param) |
| { |
| list_head_t *h = ¶m->res.net.ipaddr; |
| static char dst[INET6_ADDRSTRLEN]; |
| |
| /* Use first IP address in the CT config */ |
| if (param->g_param != NULL && !param->res.net.delall) { |
| h = ¶m->g_param->res.net.ipaddr; |
| } |
| if (list_empty(h)) { |
| h = ¶m->res.net.ipaddr; |
| if (list_empty(h)) return NULL; |
| } |
| |
| const char *addr = list_first_entry(h, ip_param, list)->val; |
| const char *mask = strchr(addr, '/'); |
| if (!mask) return addr; |
| return strncpy(dst, addr, mask - addr); |
| } |
| |
| static int vps_hostnm_configure(vps_handler *h, envid_t veid, |
| dist_actions *actions, const char *root, |
| char *hostname, const char *ipaddr, int state) |
| { |
| char *envp[5]; |
| const char *script; |
| int ret; |
| char vps_state[32]; |
| char hostnm[STR_SIZE]; |
| const char *str_state; |
| char ipaddr_s[STR_SIZE]; |
| |
| if (hostname == NULL) |
| return 0; |
| script = actions->set_hostname; |
| if (script == NULL) { |
| logger(0, 0, "Warning: set_hostname action script is not" |
| " specified"); |
| return 0; |
| } |
| str_state = state2str(state); |
| snprintf(vps_state, sizeof(vps_state), "VE_STATE=%s", str_state); |
| envp[0] = vps_state; |
| snprintf(hostnm, sizeof(hostnm), "HOSTNM=%s", hostname); |
| envp[1] = hostnm; |
| envp[2] = ENV_PATH; |
| if (ipaddr) { |
| size_t len = strnlen(ipaddr, sizeof ipaddr_s - 9) + 1; |
| memcpy(ipaddr_s, "IP_ADDR=", 8); |
| memcpy(ipaddr_s + 8, ipaddr, len); |
| envp[3] = ipaddr_s; |
| } else { |
| envp[3] = NULL; |
| } |
| envp[4] = NULL; |
| ret = vps_exec_script(h, veid, root, NULL, envp, script, DIST_FUNC, |
| SCRIPT_EXEC_TIMEOUT); |
| |
| return ret; |
| } |
| |
| struct resolv_conf_data { |
| int set; |
| char *nameserver; |
| char *searchdomain; |
| }; |
| |
| /* Read nameserver and search/domain lines from HW's /etc/resolv.conf |
| * |
| * We have to process resolv.conf the same way as system does, |
| * so this function is partly based on GLIBC implementation |
| * (resolv/res_init.c:__res_vinit()) which in turn |
| * is taken from BIND 8. |
| */ |
| static int read_resolv_conf(struct resolv_conf_data *r) |
| { |
| const char *file = "/etc/resolv.conf"; |
| FILE *fp; |
| char buf[STR_SIZE]; |
| char nsrv[STR_SIZE] = ""; |
| char *srch = NULL; |
| |
| if (r->set) |
| return 0; |
| |
| r->set = 1; |
| |
| #define MATCH(line, name) \ |
| (strncmp((line), (name), sizeof(name) - 1) == 0 && \ |
| (line[sizeof(name) - 1] == ' ' || \ |
| line[sizeof(name) - 1] == '\t')) |
| |
| fp = fopen(file, "r"); |
| if (!fp) { |
| logger(-1, errno, "Can't open %s", file); |
| return -1; |
| } |
| while (fgets(buf, sizeof(buf), fp) != NULL) { |
| char *cp, *ep; |
| |
| /* skip comments */ |
| if (*buf == ';' || *buf == '#') |
| continue; |
| /* read default domain name */ |
| if (MATCH(buf, "domain") || MATCH(buf, "search")) { |
| cp = buf + 6; |
| while (*cp == ' ' || *cp == '\t') |
| cp++; |
| if ((*cp == '\0') || (*cp == '\n')) |
| continue; |
| if ((ep = strchr(cp, '\n')) != NULL) |
| *ep = '\0'; |
| /* As per resolv.conf(5), if there's more than |
| * one line, only the last line is used |
| */ |
| if (srch) |
| free(srch); |
| srch = strdup(cp); |
| continue; |
| } |
| if (MATCH(buf, "nameserver")) { |
| cp = buf + 10; |
| while (*cp == ' ' || *cp == '\t') |
| cp++; |
| if ((*cp == '\0') || (*cp == '\n')) |
| continue; |
| if ((ep = strchr(cp, '\n')) != NULL) |
| *ep = '\0'; |
| /* nameserver entries are accumulated */ |
| if (nsrv[0]) |
| *--cp=' '; |
| strncat(nsrv, cp, sizeof(nsrv) - strlen(nsrv) - 1); |
| continue; |
| } |
| } |
| #undef MATCH |
| |
| fclose(fp); |
| |
| asprintf(&r->nameserver, "NAMESERVER=%s", nsrv); |
| asprintf(&r->searchdomain, "SEARCHDOMAIN=%s", srch); |
| free(srch); |
| |
| return 0; |
| } |
| |
| static void free_resolv_conf(struct resolv_conf_data *r) |
| { |
| if (r->nameserver) |
| free(r->nameserver); |
| if (r->searchdomain) |
| free(r->searchdomain); |
| } |
| |
| static int vps_dns_configure(vps_handler *h, envid_t veid, |
| dist_actions *actions, const char *root, misc_param *net, int state) |
| { |
| char *envp[10]; |
| char *str; |
| const char *script; |
| int ret, i; |
| struct resolv_conf_data resolv_conf = {}; |
| |
| if (list_empty(&net->searchdomain) && |
| list_empty(&net->nameserver)) |
| { |
| return 0; |
| } |
| script = actions->set_dns; |
| if (script == NULL) { |
| logger(0, 0, "Warning: set_dns action script is not specified"); |
| return 0; |
| } |
| i = 0; |
| if (!list_empty(&net->searchdomain)) { |
| str = list_first_entry(&net->searchdomain, str_param, list)->val; |
| if (strcmp(str, "inherit") == 0) { |
| read_resolv_conf(&resolv_conf); |
| str = strdup(resolv_conf.searchdomain); |
| } |
| else |
| str = list2str("SEARCHDOMAIN", &net->searchdomain); |
| |
| if (str != NULL) |
| envp[i++] = str; |
| } |
| if (!list_empty(&net->nameserver)) { |
| str = list_first_entry(&net->nameserver, str_param, list)->val; |
| if (strcmp(str, "inherit") == 0) { |
| read_resolv_conf(&resolv_conf); |
| str = strdup(resolv_conf.nameserver); |
| } |
| else |
| str = list2str("NAMESERVER", &net->nameserver); |
| |
| if (str != NULL) |
| envp[i++] = str; |
| } |
| envp[i++] = strdup(ENV_PATH); |
| envp[i] = NULL; |
| ret = vps_exec_script(h, veid, root, NULL, envp, script, DIST_FUNC, |
| SCRIPT_EXEC_TIMEOUT); |
| free_arg(envp); |
| free_resolv_conf(&resolv_conf); |
| |
| return ret; |
| } |
| |
| int vps_pw_configure(vps_handler *h, envid_t veid, dist_actions *actions, |
| const char *root, list_head_t *pw) |
| { |
| char *envp[3]; |
| const char *script; |
| int ret; |
| char *str; |
| |
| if (list_empty(pw) || actions == NULL) |
| return 0; |
| script = actions->set_userpass; |
| if (script == NULL) { |
| logger(0, 0, "Warning: set_userpass action script is not" |
| " specified"); |
| return 0; |
| } |
| str = list2str("USERPW", pw); |
| envp[0] = str; |
| envp[1] = ENV_PATH; |
| envp[2] = NULL; |
| if ((ret = vps_exec_script(h, veid, root, NULL, envp, script, DIST_FUNC, |
| SCRIPT_EXEC_TIMEOUT))) |
| { |
| ret = VZ_CHANGEPASS; |
| logger(0, 0, "Password change failed"); |
| } |
| free(str); |
| return ret; |
| } |
| |
| static int vps_quota_configure(vps_handler *h, envid_t veid, |
| const dist_actions *actions, const fs_param *fs, const dq_param *dq, |
| int state) |
| { |
| char *envp[6]; |
| const char *script; |
| int ret, i; |
| char buf[64]; |
| const char *str_state; |
| |
| if (dq->enable == NO) |
| return 0; |
| if (dq->ugidlimit == NULL) |
| return 0; |
| script = actions->set_ugid_quota; |
| if (script == NULL) { |
| logger(0, 0, "Warning: set_ugid_quota action script is not" |
| " specified"); |
| return 0; |
| } |
| i = 0; |
| str_state = state2str(state); |
| snprintf(buf, sizeof(buf), "VE_STATE=%s", str_state); |
| envp[i++] = strdup(buf); |
| if (*dq->ugidlimit) { |
| if (!ve_private_is_ploop(fs)) { |
| /* simfs/vzquota case */ |
| struct stat st; |
| const char *fs_name; |
| |
| if (stat(fs->root, &st)) { |
| logger(-1, errno, "Unable to stat %s", |
| fs->root); |
| return VZ_ERROR_SET_USER_QUOTA; |
| } |
| fs_name = vz_fs_get_name(); |
| snprintf(buf, sizeof(buf), "DEVFS=%s", fs_name); |
| envp[i++] = strdup(buf); |
| snprintf(buf, sizeof(buf), "MINOR=%d", minor(st.st_dev)); |
| envp[i++] = strdup(buf); |
| snprintf(buf, sizeof(buf), "MAJOR=%d", major(st.st_dev)); |
| envp[i++] = strdup(buf); |
| } |
| else { |
| /* ploop/native quota case */ |
| snprintf(buf, sizeof(buf), "UGIDLIMIT=%lu", |
| *dq->ugidlimit); |
| envp[i++] = strdup(buf); |
| } |
| } |
| envp[i++] = strdup(ENV_PATH); |
| envp[i] = NULL; |
| ret = vps_exec_script(h, veid, fs->root, NULL, envp, script, DIST_FUNC, |
| SCRIPT_EXEC_TIMEOUT); |
| free_arg(envp); |
| |
| return ret; |
| } |
| |
| int need_configure(vps_res *res) |
| { |
| return (res->misc.hostname == NULL && |
| !res->net.delall && |
| list_empty(&res->net.ipaddr) && |
| list_empty(&res->misc.nameserver) && |
| list_empty(&res->misc.searchdomain) && |
| res->dq.ugidlimit == NULL) ? |
| 0 : 1; |
| } |
| |
| /* |
| * Setup (add/delete) IP address(es) inside container |
| */ |
| int vps_ip_configure(vps_handler *h, envid_t veid, dist_actions *actions, |
| const char *root, int op, net_param *net, int state) |
| { |
| char *envp[6]; |
| char *str; |
| const char *script = NULL; |
| int ret, i; |
| char vps_state[32]; |
| char *ipv6_net = "IPV6=yes"; |
| const char *str_state; |
| char *delall = "IPDELALL=yes"; |
| |
| if (list_empty(&net->ipaddr) && !net->delall && state != STATE_STARTING) { |
| return 0; |
| } |
| if (actions == NULL) |
| return 0; |
| switch (op) { |
| case ADD: |
| script = actions->add_ip; |
| if (script == NULL) { |
| logger(0, 0, "Warning: add_ip action script" |
| " is not specified"); |
| return 0; |
| } |
| break; |
| case DEL: |
| script = actions->del_ip; |
| if (script == NULL) { |
| logger(0, 0, "Warning: del_ip action script" |
| " is not specified"); |
| return 0; |
| } |
| break; |
| } |
| i = 0; |
| str_state = state2str(state); |
| snprintf(vps_state, sizeof(vps_state), "VE_STATE=%s", str_state); |
| envp[i++] = vps_state; |
| str = list2str("IP_ADDR", &net->ipaddr); |
| if (str != NULL) |
| envp[i++] = str; |
| if (net->delall) |
| envp[i++] = delall; |
| if (net->ipv6_net == YES) |
| envp[i++] = ipv6_net; |
| envp[i++] = ENV_PATH; |
| envp[i] = NULL; |
| ret = vps_exec_script(h, veid, root, NULL, envp, script, DIST_FUNC, |
| SCRIPT_EXEC_TIMEOUT); |
| free(str); |
| |
| return ret; |
| } |
| |
| |
| |
| int vps_configure(vps_handler *h, envid_t veid, dist_actions *actions, |
| const fs_param *fs, vps_param *param, int state) |
| { |
| int ret; |
| vps_res *res = ¶m->res; |
| |
| if (!need_configure(res)) |
| return 0; |
| if (!vps_is_run(h, veid)) { |
| logger(0, 0, "Unable to configure container: not running"); |
| return VZ_VE_NOT_RUNNING; |
| } |
| if (actions == NULL) { |
| logger(0, 0, "Dist action not loaded"); |
| return -1; |
| } |
| if ((ret = vps_hostnm_configure(h, veid, actions, fs->root, |
| res->misc.hostname, get_local_ip_address(param), state))) { |
| return ret; |
| } |
| if ((ret = vps_dns_configure(h, veid, actions, fs->root, &res->misc, |
| state))) |
| { |
| return ret; |
| } |
| if ((ret = vps_quota_configure(h, veid, actions, fs, &res->dq, |
| state))) |
| { |
| return ret; |
| } |
| return 0; |
| } |