blob: ecf32a6f1f4f2d01388dac79c9a20e9e1fb713e7 [file] [log] [blame] [raw]
/*
* Copyright (C) 2000-2011, 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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <string.h>
#include <limits.h>
#ifdef __GLIBC__
#include <sys/sysmacros.h>
#else
#include <sys/mkdev.h>
#endif
#include <linux/vzcalluser.h>
#include "vzerror.h"
#include "script.h"
#include "util.h"
#include "dev.h"
#include "env.h"
#include "logger.h"
#include "script.h"
/* Create or remove an /etc/tmpfiles.d entry for systemd from Fedora 18+ */
static int create_tmpfiles_d_entry(const char *prefix,
const char *name, const char *alias,
mode_t mode, dev_t dev, int rm)
{
FILE *fp;
char buf[PATH_MAX];
snprintf(buf, sizeof(buf), "%setc/tmpfiles.d", prefix);
if (stat_file(buf) != 1)
return 0;
if (alias == NULL)
alias = name;
snprintf(buf, sizeof(buf), "%setc/tmpfiles.d/device-%s.conf",
prefix, alias);
if (rm) {
unlink(buf);
return 0;
}
logger(2, 0, "Creating %s", buf);
fp = fopen(buf, "w");
if (fp == NULL)
return vzctl_err(-1, errno, "Failed to create %s", buf);
fprintf(fp, "%c /dev/%s 0700 root root - %d:%d\n",
(mode & S_IFBLK) ? 'b' : 'c',
name, major(dev), minor(dev));
fclose(fp);
return 0;
}
/* Parameters:
* rm: 1 to remove a device
*/
int create_static_dev(const char *root, const char *name, const char *alias,
mode_t mode, dev_t dev, int rm)
{
char buf[PATH_MAX];
const char *device;
const char *prefix = "/";
static char *dirs[] = {
"/dev",
"/usr/lib/udev/devices",
"/lib/udev/devices",
"/etc/udev/devices",
};
unsigned int i;
int ret = 0;
if (name == NULL)
return 0;
if (root)
prefix = root;
device = strrchr(name, '/');
if (device == NULL)
device = name;
else
device++;
for (i = 0; i < ARRAY_SIZE(dirs); i++) {
snprintf(buf, sizeof(buf), "%s%s", prefix, dirs[i]);
if (stat_file(buf) != 1)
continue;
snprintf(buf, sizeof(buf), "%s%s/%s", prefix, dirs[i], device);
unlink(buf);
if (!rm && mknod(buf, mode, dev)) {
logger(-1, errno, "Failed to mknod %s", buf);
ret = 1;
}
}
create_tmpfiles_d_entry(prefix, device, alias, mode, dev, rm);
return ret;
}
static int dev_create(const char *root, dev_res *dev)
{
char buf[PATH_MAX];
struct stat st = {};
const int rm = (dev->mask == 0); /* perms set to none */
if (!dev->name)
return 0;
if (check_var(root, "VE_ROOT is not set"))
return VZ_VE_ROOT_NOTSET;
/* Get device information from CT0 ... */
snprintf(buf, sizeof(buf), "/dev/%s", dev->name);
if (!rm && stat(buf, &st)) {
if (errno == ENOENT)
logger(-1, 0, "Incorrect name or no such device %s",
buf);
else
logger(-1, errno, "Unable to stat device %s", buf);
return VZ_SET_DEVICES;
}
if (!rm && !S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
logger(-1, 0, "The %s is not block or character device", buf);
return VZ_SET_DEVICES;
}
/* ... and create or remove it in CT */
if (create_static_dev(root, dev->name, NULL,
st.st_mode, st.st_rdev, rm) != 0)
return VZ_SET_DEVICES;
return 0;
}
/** Allow/disallow access to devices on host system from CT.
*
* @param h CT handler.
* @param veid CT ID.
* @param root CT root.
* @param dev devices list.
* @return 0 on success.
*/
int vps_set_devperm(vps_handler *h, envid_t veid, const char *root,
dev_param *dev)
{
int ret = 0;
dev_res *res;
list_head_t *dev_h = &dev->dev;
if (list_empty(dev_h))
return 0;
if (!vps_is_run(h, veid)) {
logger(-1, 0, "Unable to apply devperm: "
"container is not running");
return VZ_VE_NOT_RUNNING;
}
logger(0, 0, "Setting devices");
list_for_each(res, dev_h, list) {
if (res->name)
if ((ret = dev_create(root, res)))
goto out;
if ((ret = h->setdevperm(h, veid, res)))
goto out;
}
out:
return ret;
}
int add_dev_param(dev_param *dev, const dev_res *res)
{
dev_res *tmp;
tmp = malloc(sizeof(*tmp));
if (tmp == NULL)
return -1;
if (list_is_init(&dev->dev))
list_head_init(&dev->dev);
memcpy(tmp, res, sizeof(*tmp));
if (res->name) {
tmp->name = strdup(res->name);
if (tmp->name == NULL) { /* ENOMEM */
free(tmp);
return -1;
}
}
list_add_tail(&tmp->list, &dev->dev);
return 0;
}
static void free_dev(list_head_t *head)
{
dev_res *cur, *tmp;
list_for_each_safe(cur, tmp, head, list) {
list_del(&cur->list);
free(cur->name);
free(cur);
}
list_head_init(head);
}
void free_dev_param(dev_param *dev)
{
free_dev(&dev->dev);
}
static int run_pci_script(envid_t veid, int op, list_head_t *pci_h,
const char *ve_root)
{
char *argv[3];
char *envp[10];
char *script;
int ret;
char buf[STR_SIZE];
int i = 0;
if (list_empty(pci_h))
return 0;
snprintf(buf, sizeof(buf), "VEID=%d", veid);
envp[i++] = strdup(buf);
snprintf(buf, sizeof(buf), "VE_ROOT=%s", ve_root);
envp[i++] = strdup(buf);
snprintf(buf, sizeof(buf), "ADD=%d", op == ADD);
envp[i++] = strdup(buf);
envp[i++] = list2str("PCI", pci_h);
envp[i++] = strdup(ENV_PATH);
envp[i] = NULL;
script = VPS_PCI;
argv[0] = script;
argv[1] = NULL;
ret = run_script(script, argv, envp, 0);
free_arg(envp);
return ret;
}
int vps_set_pci(vps_handler *h, envid_t veid, int op, const char *root,
pci_param *pci)
{
list_head_t *pci_h = &pci->list;
if (list_empty(pci_h))
return 0;
if (!vps_is_run(h, veid)) {
logger(-1, 0, "Unable to configure PCI devices: "
"container is not running");
return VZ_VE_NOT_RUNNING;
}
logger(0, 0, "Setting PCI devices");
return run_pci_script(veid, op, pci_h, root);
}