| /* |
| * 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 <sys/types.h> |
| #include <dirent.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <sys/wait.h> |
| |
| #include "list.h" |
| #include "logger.h" |
| #include "vzconfig.h" |
| #include "vzerror.h" |
| #include "util.h" |
| #include "script.h" |
| #include "lock.h" |
| #include "modules.h" |
| #include "create.h" |
| #include "destroy.h" |
| #include "image.h" |
| #include "cleanup.h" |
| |
| #define VPS_CREATE SCRIPTDIR "/vps-create" |
| #define VPS_DOWNLOAD SBINDIR "/vztmpl-dl" |
| #define VZOSTEMPLATE "/usr/bin/vzosname" |
| |
| static int vps_postcreate(envid_t veid, vps_res *res); |
| |
| static char *get_ostemplate_name(char *ostmpl) |
| { |
| FILE *fd; |
| char buf[STR_SIZE]; |
| char *p; |
| int status; |
| |
| snprintf(buf, sizeof(buf), VZOSTEMPLATE " %s", ostmpl); |
| if ((fd = popen(buf, "r")) == NULL) { |
| logger(-1, errno, "Error in popen(%s)", buf); |
| return NULL; |
| } |
| *buf = 0; |
| while((fgets(buf, sizeof(buf), fd)) != NULL); |
| status = pclose(fd); |
| if (WEXITSTATUS(status) || *buf == 0) { |
| logger(-1, 0, "Unable to get full ostemplate name for %s", |
| ostmpl); |
| return NULL; |
| } |
| if ((p = strchr(buf, '\n'))) |
| *p = 0; |
| return strdup(buf); |
| } |
| |
| static int download_template(char *tmpl) |
| { |
| char *arg[3]; |
| char *env[] = { NULL, NULL }; |
| char *val; |
| char buf[64]; |
| |
| arg[0] = VPS_DOWNLOAD; |
| arg[1] = tmpl; |
| arg[2] = NULL; |
| val = getenv("HOME"); |
| if (val != NULL) { |
| snprintf(buf, sizeof(buf), "HOME=%s", val); |
| env[0] = buf; |
| } |
| return run_script(VPS_DOWNLOAD, arg, env, 0); |
| } |
| |
| struct destroy_ve { |
| envid_t veid; |
| char *private; |
| }; |
| |
| static void cleanup_destroy_ve(void *data) |
| { |
| struct destroy_ve *d = data; |
| |
| vps_destroy_dir(d->veid, d->private); |
| } |
| |
| #ifdef HAVE_PLOOP |
| static void cleanup_umount_ploop(void *data) |
| { |
| vzctl_umount_image((const char *)data); |
| } |
| #endif |
| |
| /* Check if a directory is a mount point |
| * |
| * Returns: |
| * 1: a mount point |
| * 0: not a mount point |
| * -1: error |
| */ |
| int is_mount_point(const char *dir) |
| { |
| struct stat st1, st2; |
| char parent[PATH_MAX]; |
| |
| if (!dir) |
| return -1; |
| |
| if (stat(dir, &st1)) { |
| if (errno == ENOENT) /* not a mount point */ |
| return 0; |
| logger(-1, errno, "stat(%s)", dir); |
| return -1; |
| } |
| |
| if (!S_ISDIR(st1.st_mode)) { |
| logger(-1, 0, "Path %s is not a directory", dir); |
| return -1; |
| } |
| |
| snprintf(parent, sizeof(parent), "%s/..", dir); |
| if (stat(parent, &st2)) { |
| logger(-1, errno, "stat(%s)", parent); |
| return -1; |
| } |
| |
| return (st1.st_dev != st2.st_dev); |
| } |
| |
| static int fs_create(envid_t veid, vps_handler *h, vps_param *vps_p) |
| { |
| char tarball[PATH_LEN]; |
| char tmp_dir[PATH_LEN]; |
| char buf[PATH_LEN]; |
| char *private; |
| int ret; |
| char *arg[2]; |
| char *env[6]; |
| int quota = 0; |
| int i; |
| char *untar_to; |
| const char *ext[] = { "", ".gz", ".bz2", ".xz", NULL }; |
| const char *errmsg_ext = "[.gz|.bz2|.xz]"; |
| dq_param *dq = &vps_p->res.dq; |
| int layout = vps_p->opt.layout; |
| fs_param *fs = &vps_p->res.fs; |
| tmpl_param *tmpl = &vps_p->res.tmpl; |
| unsigned long uid_offset = 0; |
| unsigned long gid_offset = 0; |
| int ploop = (layout == VE_LAYOUT_PLOOP); |
| struct destroy_ve ddata; |
| struct vzctl_cleanup_handler *ch; |
| #ifdef HAVE_PLOOP |
| struct vzctl_cleanup_handler *ploop_ch = NULL; |
| #endif |
| |
| /* |
| * All other users will test directly for h->can_join_userns. Create |
| * is special, because we still don't have the container config file |
| * yet, and the user may be requesting it to be disabled in the command |
| * line. So that value may be outdated. |
| * |
| * By now cmd_p is already merged into vps_p. So what we need to do is |
| * just to test it again. We will force it to false if it is disabled |
| * here, or keep the old value otherwise. |
| */ |
| if (!(vps_p->res.misc.local_uid) || (!(*vps_p->res.misc.local_uid))) |
| h->can_join_userns = 0; |
| |
| if (h->can_join_userns && vps_p->res.misc.local_uid) { |
| uid_offset = *vps_p->res.misc.local_uid; |
| if (uid_offset && vps_p->res.misc.local_gid) |
| gid_offset = *vps_p->res.misc.local_gid; |
| } |
| |
| if (ploop && (!dq->diskspace || dq->diskspace[1] <= 0)) { |
| logger(-1, 0, "Error: diskspace not set (required for ploop)"); |
| return VZ_DISKSPACE_NOT_SET; |
| } |
| find: |
| for (i = 0; ext[i] != NULL; i++) { |
| snprintf(tarball, sizeof(tarball), "%s/cache/%s.tar%s", |
| fs->tmpl, tmpl->ostmpl, ext[i]); |
| logger(1, 0, "Looking for %s", tarball); |
| if (stat_file(tarball) == 1) |
| break; |
| } |
| if (ext[i] == NULL) { |
| if (download_template(tmpl->ostmpl) == 0) |
| goto find; |
| logger(-1, 0, "Cached OS template %s/cache/%s.tar%s not found", |
| fs->tmpl, tmpl->ostmpl, errmsg_ext); |
| return VZ_OSTEMPLATE_NOT_FOUND; |
| } |
| /* Lock CT area */ |
| if (make_dir(fs->private, 0)) |
| return VZ_FS_NEW_VE_PRVT; |
| |
| switch (is_mount_point(fs->private)) { |
| case 0: |
| snprintf(tmp_dir, sizeof(tmp_dir), |
| "%s.tmp", fs->private); |
| private = tmp_dir; |
| break; |
| case 1: |
| /* VE_PRIVATE is a mount point, so we can't use |
| * tmp_dir; have to use VE_PRIVATE directly |
| */ |
| private = fs->private; |
| tmp_dir[0] = '\0'; |
| break; |
| case -1: |
| ret = VZ_FS_NEW_VE_PRVT; |
| goto err; |
| break; |
| } |
| |
| if (tmp_dir[0] && stat_file(tmp_dir) == 1) { |
| logger(-1, 0, "Warning: Temp dir %s already exists, deleting", |
| tmp_dir); |
| if (del_dir(tmp_dir)) { |
| ret = VZ_FS_NEW_VE_PRVT; |
| goto err; |
| } |
| } |
| if (make_dir(private, 1)) { |
| logger(-1, errno, "Unable to create directory %s", private); |
| ret = VZ_FS_NEW_VE_PRVT; |
| goto err; |
| } |
| untar_to = private; |
| |
| ddata.veid = veid; |
| ddata.private = private; |
| ch = add_cleanup_handler(cleanup_destroy_ve, &ddata); |
| |
| if (ploop) { |
| #ifndef HAVE_PLOOP |
| ret = VZ_PLOOP_UNSUP; |
| goto err; |
| #else |
| /* Create and mount ploop image */ |
| struct vzctl_create_image_param param = {}; |
| struct vzctl_mount_param mount_param = {}; |
| int ploop_mode = vps_p->opt.mode; |
| |
| if (ploop_mode < 0) |
| ploop_mode = PLOOP_EXPANDED_MODE; |
| param.mode = ploop_mode; |
| param.size = dq->diskspace[1]; // limit |
| if (dq->diskinodes) |
| param.inodes = dq->diskinodes[1]; |
| ret = vzctl_create_image(private, ¶m); |
| if (ret) |
| goto err; |
| |
| if (make_dir(fs->root, 1)) { |
| logger(-1, 0, "Can't create mount point %s", fs->root); |
| ret = VZ_FS_MPOINTCREATE; |
| goto err; |
| } |
| mount_param.target = fs->root; |
| mount_param.mount_data = fs->mount_opts; |
| ret = vzctl_mount_image(private, &mount_param); |
| if (ret) |
| goto err; |
| |
| /* If interrupted, umount ploop */ |
| ploop_ch = add_cleanup_handler(cleanup_umount_ploop, &private); |
| |
| untar_to = fs->root; |
| #endif |
| } |
| if (!ploop && |
| dq != NULL && |
| dq->enable == YES && |
| dq->diskspace != NULL && |
| dq->diskinodes != NULL) |
| { |
| if (!quota_ctl(veid, QUOTA_STAT)) |
| quota_off(veid, 0); |
| quota_ctl(veid, QUOTA_DROP); |
| quota_init(veid, private, dq); |
| quota_on(veid, private, dq); |
| quota = 1; |
| } |
| arg[0] = VPS_CREATE; |
| arg[1] = NULL; |
| snprintf(buf, sizeof(buf), "PRIVATE_TEMPLATE=%s", tarball); |
| i = 0; |
| env[i++] = strdup(buf); |
| snprintf(buf, sizeof(buf), "VE_PRVT=%s", untar_to); |
| env[i++] = strdup(buf); |
| if (!is_vz_kernel(h) && h->can_join_userns) { |
| snprintf(buf, sizeof(buf), "UID_OFFSET=%lu", uid_offset); |
| env[i++] = strdup(buf); |
| snprintf(buf, sizeof(buf), "GID_OFFSET=%lu", gid_offset); |
| env[i++] = strdup(buf); |
| } |
| env[i++] = strdup(ENV_PATH); |
| env[i] = NULL; |
| logger(0, 0, "Creating container private area (%s)", tmpl->ostmpl); |
| ret = run_script(VPS_CREATE, arg, env, 0); |
| free_arg(env); |
| #ifdef HAVE_PLOOP |
| if (ploop) |
| vzctl_umount_image(private); |
| del_cleanup_handler(ploop_ch); |
| #endif |
| if (ret) |
| goto err; |
| if (quota) { |
| if ((ret = quota_off(veid, 0)) != 0) |
| goto err; |
| if ((ret = quota_set(veid, fs->private, dq)) != 0) |
| goto err; |
| quota = 0; |
| } |
| /* Unlock CT area */ |
| rmdir(fs->private); |
| del_cleanup_handler(ch); |
| if (tmp_dir[0] && rename(tmp_dir, fs->private)) { |
| logger(-1, errno, "Can't rename %s to %s", tmp_dir, fs->private); |
| ret = VZ_FS_NEW_VE_PRVT; |
| } |
| |
| err: |
| if (ret && quota) { |
| quota_off(veid, 0); |
| quota_ctl(veid, QUOTA_DROP); |
| } |
| rmdir(fs->private); /* remove VE_PRIVATE if empty */ |
| if (tmp_dir[0]) |
| del_dir(tmp_dir); |
| |
| return ret; |
| } |
| |
| static void cleanup_destroy_file(void *data) |
| { |
| unlink(data); |
| } |
| |
| int vps_create(vps_handler *h, envid_t veid, vps_param *vps_p, vps_param *cmd_p, |
| struct mod_action *action) |
| { |
| int ret = 0; |
| char src[STR_SIZE]; |
| char dst[STR_SIZE]; |
| const char *sample_config; |
| fs_param *fs = &vps_p->res.fs; |
| tmpl_param *tmpl = &vps_p->res.tmpl; |
| vps_param *conf_p; |
| int cfg_exists; |
| char *full_ostmpl; |
| struct vzctl_cleanup_handler *ch = NULL; |
| |
| get_vps_conf_path(veid, dst, sizeof(dst)); |
| sample_config = NULL; |
| cfg_exists = (stat_file(dst) == 1); |
| if (cmd_p->opt.config != NULL) { |
| snprintf(src, sizeof(src), VPSCONFDIR "/ve-%s.conf-sample", |
| cmd_p->opt.config); |
| if (stat_file(src) != 1) { |
| logger(-1, errno, "Sample config %s not found", src); |
| ret = VZ_CP_CONFIG; |
| goto err; |
| } |
| if (cfg_exists) { |
| logger(-1, 0, "Error: container config file %s " |
| "already exists, can not use --config " |
| "option", dst); |
| ret = VZ_CP_CONFIG; /* FIXME */ |
| goto err; |
| } |
| sample_config = cmd_p->opt.config; |
| } else if (vps_p->opt.config != NULL) { |
| snprintf(src, sizeof(src), VPSCONFDIR "/ve-%s.conf-sample", |
| vps_p->opt.config); |
| /* Bail out if CONFIGFILE is wrong in /etc/vz/vz.conf */ |
| if (stat_file(src) != 1) { |
| logger(-1, errno, "Sample config %s not found", src); |
| logger(-1, 0, "Fix the value of CONFIGFILE in " |
| GLOBAL_CFG); |
| ret = VZ_CP_CONFIG; |
| goto err; |
| } |
| /* Do not use config if CT config exists */ |
| if (!cfg_exists) |
| sample_config = vps_p->opt.config; |
| else { |
| logger(0, 0, "Warning: CT config file already " |
| "exists, not applying a default config " |
| "sample."); |
| logger(0, 0, "It might lead to incomplete CT " |
| "configuration, you can use --applyconfig " |
| "to fix."); |
| } |
| } |
| if (sample_config != NULL) { |
| ch = add_cleanup_handler(cleanup_destroy_file, &dst); |
| if (cp_file(dst, src)) |
| { |
| ret = VZ_CP_CONFIG; |
| goto err; |
| } |
| if ((conf_p = init_vps_param()) == NULL) |
| { |
| ret = VZ_RESOURCE_ERROR; |
| goto err_cfg; |
| } |
| vps_parse_config(veid, src, conf_p, action); |
| merge_vps_param(vps_p, conf_p); |
| if (conf_p->opt.origin_sample == NULL) |
| cmd_p->opt.origin_sample = strdup(sample_config); |
| free_vps_param(conf_p); |
| } |
| merge_vps_param(vps_p, cmd_p); |
| if (check_var(fs->tmpl, "TEMPLATE is not set")) |
| { |
| ret = VZ_VE_TMPL_NOTSET; |
| goto err_cfg; |
| } |
| if (check_var(fs->private, "VE_PRIVATE is not set")) |
| { |
| ret = VZ_VE_PRIVATE_NOTSET; |
| goto err_cfg; |
| } |
| if (check_var(fs->root, "VE_ROOT is not set")) |
| { |
| ret = VZ_VE_ROOT_NOTSET; |
| goto err_cfg; |
| } |
| if (dir_empty(fs->private) != 1) { |
| logger(-1, 0, "Private area already exists in %s", fs->private); |
| ret = VZ_FS_PRVT_AREA_EXIST; |
| goto err_cfg; |
| } |
| |
| if (vps_p->opt.layout == 0) { |
| #ifdef HAVE_PLOOP |
| vps_p->opt.layout = VE_LAYOUT_PLOOP; |
| #else |
| vps_p->opt.layout = VE_LAYOUT_SIMFS; |
| #endif |
| } |
| |
| if (vps_p->opt.layout == VE_LAYOUT_PLOOP && !is_ploop_supported()) { |
| logger(-1, 0, "Alternatively, if you can't or don't want to use ploop, please"); |
| logger(-1, 0, "add --layout simfs option, or set VE_LAYOUT=simfs in " GLOBAL_CFG); |
| ret = VZ_PLOOP_UNSUP; |
| goto err_cfg; |
| } |
| |
| if (cmd_p->res.name.name) { |
| if ((ret = set_name(veid, cmd_p->res.name.name, |
| cmd_p->res.name.name))) |
| goto err_cfg; |
| } |
| |
| if (action != NULL && action->mod_count) { |
| if ((ret = mod_setup(h, veid, 0, 0, action, vps_p))) |
| goto err_private; |
| } else { |
| /* Set default ostemplate if not specified */ |
| if (cmd_p->res.tmpl.ostmpl == NULL && |
| tmpl->ostmpl == NULL && |
| tmpl->def_ostmpl != NULL) |
| { |
| tmpl->ostmpl = strdup(tmpl->def_ostmpl); |
| } |
| |
| if (check_var(tmpl->ostmpl, "OS template is not specified")) |
| { |
| ret = VZ_VE_OSTEMPLATE_NOTSET; |
| goto err_cfg; |
| } |
| if (stat_file(VZOSTEMPLATE) == 1) { |
| full_ostmpl = get_ostemplate_name(tmpl->ostmpl); |
| if (full_ostmpl != NULL) { |
| free(tmpl->ostmpl); |
| tmpl->ostmpl = full_ostmpl; |
| } |
| } |
| ret = fs_create(veid, h, vps_p); |
| if (ret) |
| goto err_root; |
| } |
| |
| if ((ret = vps_postcreate(veid, &vps_p->res))) |
| goto err_root; |
| move_config(veid, DESTR); |
| /* store root, private, ostemplate in case default used */ |
| if (cmd_p->res.fs.root_orig == NULL && |
| fs->root_orig != NULL) |
| { |
| cmd_p->res.fs.root_orig = strdup(fs->root_orig); |
| } |
| if (cmd_p->res.fs.private_orig == NULL && |
| fs->private_orig != NULL) |
| { |
| cmd_p->res.fs.private_orig = strdup(fs->private_orig); |
| } |
| /* Store full ostemplate name */ |
| if (tmpl->ostmpl != NULL) { |
| if (cmd_p->res.tmpl.ostmpl != NULL) |
| free(cmd_p->res.tmpl.ostmpl); |
| cmd_p->res.tmpl.ostmpl = strdup(basename(tmpl->ostmpl)); |
| } |
| /* For upstream kernel, in case userns is not available |
| * at the time of creation, we should store |
| * LOCAL_UID=0 and LOCAL_GID=0 to CT config, |
| * otherwise it will fail to start. |
| */ |
| if (!is_vz_kernel(h) && !h->can_join_userns) { |
| if (!cmd_p->res.misc.local_uid) |
| cmd_p->res.misc.local_uid = |
| vz_malloc(sizeof(unsigned long)); |
| if (!cmd_p->res.misc.local_uid) |
| goto err_names; |
| |
| if (!cmd_p->res.misc.local_gid) |
| cmd_p->res.misc.local_gid = |
| vz_malloc(sizeof(unsigned long)); |
| if (!cmd_p->res.misc.local_gid) |
| goto err_names; |
| |
| *cmd_p->res.misc.local_uid = 0; |
| *cmd_p->res.misc.local_gid = 0; |
| } |
| if ((ret = vps_save_config(veid, dst, cmd_p, vps_p, action))) |
| goto err_names; |
| |
| if ((ret = run_pre_script(veid, USER_CREATE_SCRIPT))) |
| { |
| logger(0, 0, "User create script " USER_CREATE_SCRIPT |
| " exited with error"); |
| goto err_names; |
| } |
| |
| del_cleanup_handler(ch); |
| logger(0, 0, "Container private area was created"); |
| return 0; |
| |
| err_names: |
| remove_names(veid); |
| err_root: |
| rmdir(fs->root); |
| err_private: |
| vps_destroy_dir(veid, fs->private); |
| err_cfg: |
| if (sample_config != NULL) |
| unlink(dst); |
| err: |
| logger(-1, 0, "Creation of container private area failed"); |
| return ret; |
| } |
| |
| static int vps_postcreate(envid_t veid, vps_res *res) |
| { |
| char buf[STR_SIZE]; |
| dist_actions actions; |
| char *dist_name; |
| char *arg[2]; |
| char *env[3]; |
| int ret; |
| |
| if (check_var(res->fs.root, "VE_ROOT is not set")) |
| return VZ_VE_ROOT_NOTSET; |
| dist_name = get_dist_name(&res->tmpl); |
| ret = read_dist_actions(dist_name, DIST_DIR, &actions); |
| free(dist_name); |
| if (ret) |
| return ret; |
| if (actions.post_create == NULL) { |
| ret = 0; |
| goto err; |
| } |
| ret = fsmount(veid, &res->fs, &res->dq, 0); |
| if (ret) |
| goto err; |
| arg[0] = actions.post_create; |
| arg[1] = NULL; |
| snprintf(buf, sizeof(buf), "VE_ROOT=%s", res->fs.root); |
| env[0] = buf; |
| env[1] = ENV_PATH; |
| env[2] = NULL; |
| logger(0, 0, "Performing postcreate actions"); |
| ret = run_script(actions.post_create, arg, env, 0); |
| fsumount(veid, &res->fs); |
| err: |
| free_dist_actions(&actions); |
| return ret; |
| } |