blob: 58334e11c0c777efdc3d05166218ab72375a66ee [file] [log] [blame] [raw]
/*
* Copyright (C) 2000-2006 SWsoft. 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 "config.h"
#include "vzerror.h"
#include "util.h"
#include "script.h"
#include "lock.h"
#include "modules.h"
#include "create.h"
#define BACKUP 0
#define DESTR 1
#define VZOSTEMPLATE "/usr/bin/vzosname"
static int destroydir(char *dir);
/* Renames (to "*.destroyed" if action == MOVE) or removes config,
* (if action == DESTR)
* Also, appropriate mount/umount scripts are linked.
*/
static int move_config(int veid, int action)
{
char conf[PATH_LEN];
char newconf[PATH_LEN];
snprintf(conf, sizeof(conf), VPS_CONF_DIR "%d.conf", veid);
snprintf(newconf, sizeof(newconf), "%s." DESTR_PREFIX, conf);
action == BACKUP ? rename(conf, newconf) : unlink(newconf);
snprintf(conf, sizeof(conf), VPS_CONF_DIR "%d." MOUNT_PREFIX, veid);
snprintf(newconf, sizeof(newconf), "%s." DESTR_PREFIX, conf);
action == BACKUP ? rename(conf, newconf) : unlink(newconf);
snprintf(conf, sizeof(conf), VPS_CONF_DIR "%d." UMOUNT_PREFIX, veid);
snprintf(newconf, sizeof(newconf), "%s." DESTR_PREFIX, conf);
action == BACKUP ? rename(conf, newconf) : unlink(newconf);
snprintf(conf, sizeof(conf), VPS_CONF_DIR "%d." START_PREFIX, veid);
snprintf(newconf, sizeof(newconf), "%s." DESTR_PREFIX, conf);
action == BACKUP ? rename(conf, newconf) : unlink(newconf);
snprintf(conf, sizeof(conf), VPS_CONF_DIR "%d." STOP_PREFIX, veid);
snprintf(newconf, sizeof(newconf), "%s." DESTR_PREFIX, conf);
action == BACKUP ? rename(conf, newconf) : unlink(newconf);
return 0;
}
int del_dir(char *dir)
{
int ret;
char *argv[4];
argv[0] = "/bin/rm";
argv[1] = "-rf";
argv[2] = dir;
argv[3] = NULL;
ret = run_script("/bin/rm", argv, NULL, 0);
return ret;
}
static int destroy(envid_t veid, char *dir)
{
int ret;
if (!quota_ctl(veid, QUOTA_STAT)) {
if ((ret = quota_off(veid, 0)))
if ((ret = quota_off(veid, 1)))
return ret;
}
quota_ctl(veid, QUOTA_DROP);
if ((ret = destroydir(dir)))
return ret;
return 0;
}
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(0, errno, "Error in popen(%s)", buf);
return NULL;
}
*buf = 0;
while((p = fgets(buf, sizeof(buf), fd)) != NULL);
status = pclose(fd);
if (WEXITSTATUS(status) || *buf == 0) {
logger(0, 0, "Unable to get full ostemplate name for %s",
ostmpl);
return NULL;
}
if ((p = strchr(buf, '\n')))
*p = 0;
return strdup(buf);
}
int fs_create(envid_t veid, fs_param *fs, tmpl_param *tmpl, dq_param *dq,
char *tar_nm)
{
char tarball[PATH_LEN];
char tmp_dir[PATH_LEN];
char buf[PATH_LEN];
int ret;
char *arg[2];
char *env[4];
int quota = 0;
snprintf(tarball, sizeof(tarball), "%s/%s.tar.gz", fs->tmpl, tar_nm);
if (!stat_file(tarball)) {
logger(0, 0, "Cached os template %s not found", tarball);
return VZ_PKGSET_NOT_FOUND;
}
/* Lock VPS area */
if (make_dir(fs->private, 0))
return VZ_FS_NEW_VE_PRVT;
snprintf(tmp_dir, sizeof(tmp_dir), "%s.tmp", fs->private);
if (stat_file(tmp_dir)) {
logger(0, 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(tmp_dir, 1)) {
logger(0, errno, "Unable to create directory %s", tmp_dir);
ret = VZ_FS_NEW_VE_PRVT;
goto err;
}
if (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, tmp_dir, dq);
quota_on(veid, tmp_dir, dq);
quota = 1;
}
arg[0] = VPS_CREATE;
arg[1] = NULL;
snprintf(buf, sizeof(buf), "PRIVATE_TEMPLATE=%s", tarball);
env[0] = strdup(buf);
snprintf(buf, sizeof(buf), "VE_PRVT=%s", tmp_dir);
env[1] = strdup(buf);
env[2] = strdup(ENV_PATH);
env[3] = NULL;
ret = run_script(VPS_CREATE, arg, env, 0);
free_arg(env);
if (ret)
goto err;
if (quota) {
quota_off(veid, 0);
quota_set(veid, fs->private, dq);
quota = 0;
}
/* Unlock VPS area */
rmdir(fs->private);
if (rename(tmp_dir, fs->private)) {
logger(0, 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);
rmdir(tmp_dir);
return ret;
}
int vps_create(vps_handler *h, envid_t veid, vps_param *vps_p, vps_param *cmd_p,
struct mod_action *action)
{
int ret;
char tar_nm[256];
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;
get_vps_conf_path(veid, dst, sizeof(dst));
sample_config = NULL;
cfg_exists = 0;
if (stat_file(dst))
cfg_exists = 1;
if (cmd_p->opt.config != NULL) {
snprintf(src, sizeof(src), VPS_CONF_DIR "ve-%s.conf-sample",
cmd_p->opt.config);
if (!stat_file(src)) {
logger(0, 0, "File %s is not found", src);
return VZ_CP_CONFIG;
}
if (cfg_exists) {
logger(0, 0, "Warning: VPS config file already exists,"
" will be rewrited with %s", src);
unlink(dst);
}
sample_config = cmd_p->opt.config;
} else if (vps_p->opt.config != NULL) {
snprintf(src, sizeof(src), VPS_CONF_DIR "ve-%s.conf-sample",
vps_p->opt.config);
/* Do not use config if VPS config exists */
if (!cfg_exists && stat_file(src))
sample_config = vps_p->opt.config;
}
if (sample_config != NULL) {
if (cp_file(dst, src))
return VZ_CP_CONFIG;
if ((conf_p = init_vps_param()) == NULL)
return VZ_RESOURCE_ERROR;
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);
}
if (check_var(fs->tmpl, "TEMPLATE is not set"))
return VZ_VE_TMPL_NOTSET;
if (check_var(fs->private, "VE_PRIVATE is not set"))
return VZ_VE_PRIVATE_NOTSET;
if (check_var(fs->root, "VE_ROOT is not set"))
return VZ_VE_ROOT_NOTSET;
if (stat_file(fs->private)) {
logger(0, 0, "Private area already exists in %s", fs->private);
return VZ_FS_PRVT_AREA_EXIST;
}
merge_vps_param(vps_p, cmd_p);
logger(0, 0, "Creating VPS private area: %s", fs->private);
if (action != NULL && action->mod_count) {
ret = mod_setup(h, veid, 0, 0, action, vps_p);
} 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"))
return VZ_VE_PKGSET_NOTSET;
if (stat_file(VZOSTEMPLATE)) {
full_ostmpl = get_ostemplate_name(tmpl->ostmpl);
if (full_ostmpl != NULL) {
free(tmpl->ostmpl);
tmpl->ostmpl = full_ostmpl;
}
}
snprintf(tar_nm, sizeof(tar_nm), "cache/%s", tmpl->ostmpl);
ret = fs_create(veid, fs, tmpl, &vps_p->res.dq, tar_nm);
}
if (ret) {
if (sample_config != NULL)
unlink(dst);
destroy(veid, fs->private);
logger(0, 0, "Creation of VPS private area failed");
return ret;
}
vps_postcreate(veid, &vps_p->res.fs, &vps_p->res.tmpl);
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(tmpl->ostmpl);
}
vps_save_config(veid, dst, cmd_p, vps_p, action);
logger(0, 0, "VPS private area was created");
return 0;
}
int vps_postcreate(envid_t veid, fs_param *fs, tmpl_param *tmpl)
{
char buf[STR_SIZE];
dist_actions actions;
char *dist_name;
char *arg[2];
char *env[3];
int ret;
if (check_var(fs->root, "VE_ROOT is not set"))
return VZ_VE_ROOT_NOTSET;
dist_name = get_dist_name(tmpl);
ret = read_dist_actions(dist_name, DIST_DIR, &actions);
if (dist_name != NULL)
free(dist_name);
if (ret)
return ret;
if (actions.post_create == NULL) {
ret = 0;
goto err;
}
ret = fsmount(veid, fs, NULL);
if (ret)
goto err;
arg[0] = actions.post_create;
arg[1] = NULL;
snprintf(buf, sizeof(buf), "VE_ROOT=%s", 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, fs->root);
err:
free_dist_actions(&actions);
return ret;
}
static char *get_destroy_root(char *dir)
{
struct stat st;
int id, len;
char *p, *prev;
char tmp[STR_SIZE];
if (stat(dir, &st) < 0)
return NULL;
id = st.st_dev;
p = dir + strlen(dir) - 1;
prev = p;
while (p > dir) {
while (p > dir && (*p == '/' || *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;
}
char *maketmpdir(const char *dir)
{
char buf[STR_SIZE];
char *tmp;
char *tmp_dir;
int len;
snprintf(buf, sizeof(buf), "%s/XXXXXXX", dir);
if ((tmp = mkdtemp(buf)) == NULL) {
logger(0, errno, "Error in mkdtemp(%s)", buf);
return NULL;
}
len = strlen(dir);
tmp_dir = (char *)malloc(strlen(tmp) - len);
if (tmp_dir == NULL)
return NULL;
strcpy(tmp_dir, tmp + len + 1);
return tmp_dir;
}
static void _destroydir(char *root)
{
char buf[STR_SIZE];
struct stat st;
struct dirent *ep;
DIR *dp;
int del, ret;
do {
if (!(dp = opendir(root)))
return;
del = 0;
while ((ep = readdir(dp))) {
if (!strcmp(ep->d_name, ".") ||
!strcmp(ep->d_name, ".."))
{
continue;
}
snprintf(buf, sizeof(buf), "%s/%s", root, ep->d_name);
if (stat(buf, &st))
continue;
if (!S_ISDIR(st.st_mode))
continue;
snprintf(buf, sizeof(buf), "rm -rf %s/%s",
root, ep->d_name);
ret = system(buf);
if (ret == -1 || WEXITSTATUS(ret))
sleep(10);
del = 1;
}
closedir(dp);
} while(del);
}
static int destroydir(char *dir)
{
char buf[STR_SIZE];
char tmp[STR_SIZE];
char *root;
char *tmp_nm;
int fd_lock, pid;
struct sigaction act, actold;
int ret = 0;
struct stat st;
if (stat(dir, &st)) {
if (errno != ENOENT) {
logger(0, errno, "Unable to stat %s", dir);
return -1;
}
return 0;
}
if (!S_ISDIR(st.st_mode)) {
logger(0, 0, "Warning: VPS private area is not a directory");
if (unlink(dir)) {
logger(0, errno, "Unable to unlink %s", dir);
return -1;
}
return 0;
}
root = get_destroy_root(dir);
if (root == NULL) {
logger(0, 0, "Unable to get root for %s", dir);
return -1;
}
snprintf(tmp, sizeof(buf), "%s/tmp", root);
free(root);
if (!stat_file(tmp)) {
if (mkdir(tmp, 0755)) {
logger(0, errno, "Can't create tmp dir %s", tmp);
return VZ_FS_DEL_PRVT;
}
}
/* First move to del */
if ((tmp_nm = maketmpdir(tmp)) == NULL) {
logger(0, 0, "Unable to generate temporary name in %s", tmp);
return VZ_FS_DEL_PRVT;
}
snprintf(buf, sizeof(tmp), "%s/%s", tmp, tmp_nm);
free(tmp_nm);
if (rename(dir, buf)) {
logger(0, errno, "Can't move %s -> %s", dir, buf);
rmdir(buf);
return VZ_FS_DEL_PRVT;
}
snprintf(buf, sizeof(buf), "%s/rm.lck", tmp);
if ((fd_lock = _lock(buf, 0)) == -2) {
/* Already locked */
_unlock(fd_lock, NULL);
return 0;
} else if (fd_lock == -1)
return VZ_FS_DEL_PRVT;
sigaction(SIGCHLD, NULL, &actold);
sigemptyset(&act.sa_mask);
act.sa_handler = SIG_IGN;
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, NULL);
if (!(pid = fork())) {
int fd;
setsid();
fd = open("/dev/null", O_WRONLY);
if (fd != -1) {
close(0);
close(1);
close(2);
dup2(fd, 1);
dup2(fd, 2);
}
_destroydir(tmp);
_unlock(fd_lock, buf);
exit(0);
} else if (pid < 0) {
logger(0, errno, "destroydir: Unable to fork");
ret = VZ_RESOURCE_ERROR;
}
sleep(1);
sigaction(SIGCHLD, &actold, NULL);
return ret;
}
int vps_destroy(vps_handler *h, envid_t veid, fs_param *fs)
{
int ret;
if (check_var(fs->private, "VE_PRIVATE is not set"))
return VZ_VE_PRIVATE_NOTSET;
if (check_var(fs->root, "VE_ROOT is not set"))
return VZ_VE_ROOT_NOTSET;
if (vps_is_mounted(fs->root)) {
logger(0, 0, "VPS is currently mounted (umount first)");
return VZ_FS_MOUNTED;
}
logger(0, 0, "Destroying VPS private area: %s", fs->private);
if ((ret = destroy(veid, fs->private)))
return ret;
move_config(veid, BACKUP);
rmdir(fs->root);
logger(0, 0, "VPS private area was destroyed");
return 0;
}