| /* |
| * 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 "config.h" |
| #include "vzerror.h" |
| #include "util.h" |
| #include "script.h" |
| #include "lock.h" |
| #include "modules.h" |
| #include "create.h" |
| #include "env.h" |
| |
| #define BACKUP 0 |
| #define DESTR 1 |
| |
| const char destroy_dir_magic[]="vzctl-rm-me."; |
| |
| static int destroydir(char *dir); |
| |
| 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; |
| } |
| |
| int vps_destroy_dir(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_destroy_root(const char *dir) |
| { |
| struct stat st; |
| dev_t id; |
| int len; |
| const 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/%sXXXXXXX", dir, destroy_dir_magic); |
| if ((tmp = mkdtemp(buf)) == NULL) { |
| logger(-1, 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; |
| } |
| |
| /* Removes all the directories under 'root' |
| * those names start with 'destroy_dir_magic' |
| */ |
| static void _destroydir(const 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 (strncmp(ep->d_name, destroy_dir_magic, |
| sizeof(destroy_dir_magic) - 1)) |
| { |
| 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(-1, errno, "Unable to stat %s", dir); |
| return -1; |
| } |
| return 0; |
| } |
| if (!S_ISDIR(st.st_mode)) { |
| logger(-1, 0, "Warning: container private area " |
| "is not a directory"); |
| if (unlink(dir)) { |
| logger(-1, errno, "Unable to unlink %s", dir); |
| return -1; |
| } |
| return 0; |
| } |
| root = get_destroy_root(dir); |
| if (root == NULL) { |
| logger(-1, 0, "Unable to get root for %s", dir); |
| return -1; |
| } |
| snprintf(tmp, sizeof(tmp), "%s/vztmp", root); |
| free(root); |
| if (!stat_file(tmp)) { |
| if (mkdir(tmp, 0755)) { |
| logger(-1, errno, "Can't create tmp dir %s", tmp); |
| return VZ_FS_DEL_PRVT; |
| } |
| } |
| /* Fast/async removal -- first move to tmp dir */ |
| if ((tmp_nm = maketmpdir(tmp)) == NULL) { |
| logger(-1, 0, "Unable to generate temporary name in %s", tmp); |
| return VZ_FS_DEL_PRVT; |
| } |
| snprintf(buf, sizeof(buf), "%s/%s", tmp, tmp_nm); |
| free(tmp_nm); |
| if (rename(dir, buf)) { |
| rmdir(buf); |
| if (errno == EXDEV) { |
| /* See http://bugzilla.openvz.org/457 */ |
| logger(0, 0, "Warning: directory %s is not on the same" |
| " filesystem as %s - doing slow/sync" |
| " removal", dir, tmp); |
| if (del_dir(dir)) |
| return VZ_FS_DEL_PRVT; |
| else |
| return 0; |
| } else { |
| logger(-1, errno, "Can't move %s -> %s", dir, 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(-1, 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_run(h, veid)) { |
| logger(0, 0, "Container is currently running." |
| " Stop it first."); |
| return VZ_VE_RUNNING; |
| } |
| if (vps_is_mounted(fs->root)) { |
| logger(0, 0, "Container is currently mounted (umount first)"); |
| return VZ_FS_MOUNTED; |
| } |
| logger(0, 0, "Destroying container private area: %s", fs->private); |
| if ((ret = vps_destroy_dir(veid, fs->private))) |
| return ret; |
| move_config(veid, BACKUP); |
| rmdir(fs->root); |
| logger(0, 0, "Container private area was destroyed"); |
| |
| return 0; |
| } |