Files
xen/tools/blktap/drivers/blktapctrl.c
T
kfraser@localhost.localdomain 4eb73854d7 Fix file descriptor leak in blktapctrl
The blktapctrl process is responsible for spawning individual tapdisk
processes. It does this using the 'system' method, but unfortunately
none of its file descriptors have the close-on-exec flag set. The
parent blktapctl process opens a couple of unix domain sockets
per-tapdisk it spawns. So the first tapdisk get 2 FDs leaked to it,
the second gets 4 FDs leaked to it, the 3rd gets 6 and so on. The use
of 'system' also unneccessarily invokes the shell.

Replace system with fork/execvp, and explicitly close all file handles
up to _SC_OPEN_MAX.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2007-08-01 09:04:20 +01:00

807 lines
19 KiB
C

/*
* blktapctrl.c
*
* userspace controller for the blktap disks.
* As requests for new block devices arrive,
* the controller spawns off a separate process
* per-disk.
*
*
* Copyright (c) 2005 Julian Chesterfield and Andrew Warfield.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation; or, when distributed
* separately from the Linux kernel or incorporated into other
* software packages, subject to the following license:
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this source file (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/user.h>
#include <err.h>
#include <errno.h>
#include <sys/types.h>
#include <linux/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
#include <xs.h>
#include <printf.h>
#include <sys/time.h>
#include <syslog.h>
#include "blktaplib.h"
#include "blktapctrl.h"
#include "tapdisk.h"
#define PIDFILE "/var/run/blktapctrl.pid"
#define NUM_POLL_FDS 2
#define MSG_SIZE 4096
#define MAX_TIMEOUT 10
#define MAX_RAND_VAL 0xFFFF
#define MAX_ATTEMPTS 10
int run = 1;
int max_timeout = MAX_TIMEOUT;
int ctlfd = 0;
int blktap_major;
static int open_ctrl_socket(char *devname);
static int write_msg(int fd, int msgtype, void *ptr, void *ptr2);
static int read_msg(int fd, int msgtype, void *ptr);
static driver_list_entry_t *active_disks[MAX_DISK_TYPES];
static void init_driver_list(void)
{
int i;
for (i = 0; i < MAX_DISK_TYPES; i++)
active_disks[i] = NULL;
return;
}
static void init_rng(void)
{
static uint32_t seed;
struct timeval tv;
gettimeofday(&tv, NULL);
seed = tv.tv_usec;
srand48(seed);
return;
}
static void make_blktap_dev(char *devname, int major, int minor)
{
struct stat st;
if (lstat(devname, &st) != 0) {
/*Need to create device*/
if (mkdir(BLKTAP_DEV_DIR, 0755) == 0)
DPRINTF("Created %s directory\n",BLKTAP_DEV_DIR);
if (mknod(devname, S_IFCHR|0600,
makedev(major, minor)) == 0)
DPRINTF("Created %s device\n",devname);
} else {
DPRINTF("%s device already exists\n",devname);
/* it already exists, but is it the same major number */
if (((st.st_rdev>>8) & 0xff) != major) {
DPRINTF("%s has old major %d\n",
devname,
(unsigned int)((st.st_rdev >> 8) & 0xff));
/* only try again if we succed in deleting it */
if (!unlink(devname))
make_blktap_dev(devname, major, minor);
}
}
}
static int get_new_dev(int *major, int *minor, blkif_t *blkif)
{
domid_translate_t tr;
int ret;
char *devname;
tr.domid = blkif->domid;
tr.busid = (unsigned short)blkif->be_id;
ret = ioctl(ctlfd, BLKTAP_IOCTL_NEWINTF, tr );
if ( (ret <= 0)||(ret > MAX_TAP_DEV) ) {
DPRINTF("Incorrect Dev ID [%d]\n",ret);
return -1;
}
*minor = ret;
*major = ioctl(ctlfd, BLKTAP_IOCTL_MAJOR, ret );
if (*major < 0) {
DPRINTF("Incorrect Major ID [%d]\n",*major);
return -1;
}
if (asprintf(&devname,"%s/%s%d",BLKTAP_DEV_DIR, BLKTAP_DEV_NAME, *minor) == -1)
return -1;
make_blktap_dev(devname,*major,*minor);
DPRINTF("Received device id %d and major %d, "
"sent domid %d and be_id %d\n",
*minor, *major, tr.domid, tr.busid);
return 0;
}
static int get_tapdisk_pid(blkif_t *blkif)
{
int ret;
if ((ret = write_msg(blkif->fds[WRITE], CTLMSG_PID, blkif, NULL))
<= 0) {
DPRINTF("Write_msg failed - CTLMSG_PID(%d)\n", ret);
return -EINVAL;
}
if ((ret = read_msg(blkif->fds[READ], CTLMSG_PID_RSP, blkif))
<= 0) {
DPRINTF("Read_msg failure - CTLMSG_PID(%d)\n", ret);
return -EINVAL;
}
return 1;
}
/* Look up the disk specified by path:
* if found, dev points to the device string in the path
* type is the tapdisk driver type id
* blkif is the existing interface if this is a shared driver
* and NULL otherwise.
* return 0 on success, -1 on error.
*/
static int test_path(char *path, char **dev, int *type, blkif_t **blkif)
{
char *ptr, handle[10];
int i, size, found = 0;
size = sizeof(dtypes)/sizeof(disk_info_t *);
*type = MAX_DISK_TYPES + 1;
*blkif = NULL;
if ( (ptr = strstr(path, ":"))!=NULL) {
memcpy(handle, path, (ptr - path));
*dev = ptr + 1;
ptr = handle + (ptr - path);
*ptr = '\0';
DPRINTF("Detected handle: [%s]\n",handle);
for (i = 0; i < size; i++)
if (strncmp(handle, dtypes[i]->handle,
(ptr - path)) ==0) {
found = 1;
break;
}
if (found) {
*type = dtypes[i]->idnum;
if (dtypes[i]->single_handler == 1) {
/* Check whether tapdisk process
already exists */
if (active_disks[dtypes[i]->idnum] == NULL)
*blkif = NULL;
else
*blkif = active_disks[dtypes[i]
->idnum]->blkif;
}
return 0;
}
}
/* Fall-through case, we didn't find a disk driver. */
DPRINTF("Unknown blktap disk type [%s]!\n",handle);
*dev = NULL;
return -1;
}
static void add_disktype(blkif_t *blkif, int type)
{
driver_list_entry_t *entry, **pprev;
if (type > MAX_DISK_TYPES)
return;
entry = malloc(sizeof(driver_list_entry_t));
entry->blkif = blkif;
entry->next = NULL;
pprev = &active_disks[type];
while (*pprev != NULL)
pprev = &(*pprev)->next;
*pprev = entry;
entry->pprev = pprev;
}
static int del_disktype(blkif_t *blkif)
{
driver_list_entry_t *entry, **pprev;
int type = blkif->drivertype, count = 0, close = 0;
if (type > MAX_DISK_TYPES)
return 1;
pprev = &active_disks[type];
while ((*pprev != NULL) && ((*pprev)->blkif != blkif))
pprev = &(*pprev)->next;
if ((entry = *pprev) == NULL) {
DPRINTF("DEL_DISKTYPE: No match\n");
return 1;
}
*pprev = entry->next;
if (entry->next)
entry->next->pprev = pprev;
DPRINTF("DEL_DISKTYPE: Freeing entry\n");
free(entry);
/* Caller should close() if no single controller, or list is empty. */
return (!dtypes[type]->single_handler || (active_disks[type] == NULL));
}
static int write_msg(int fd, int msgtype, void *ptr, void *ptr2)
{
blkif_t *blkif;
blkif_info_t *blk;
msg_hdr_t *msg;
msg_newdev_t *msg_dev;
char *p, *buf, *path;
int msglen, len, ret;
fd_set writefds;
struct timeval timeout;
image_t *image, *img;
uint32_t seed;
blkif = (blkif_t *)ptr;
blk = blkif->info;
image = blkif->prv;
len = 0;
switch (msgtype)
{
case CTLMSG_PARAMS:
path = (char *)ptr2;
DPRINTF("Write_msg called: CTLMSG_PARAMS, sending [%s, %s]\n",
blk->params, path);
msglen = sizeof(msg_hdr_t) + strlen(path) + 1;
buf = malloc(msglen);
/*Assign header fields*/
msg = (msg_hdr_t *)buf;
msg->type = CTLMSG_PARAMS;
msg->len = msglen;
msg->drivertype = blkif->drivertype;
msg->readonly = blkif->readonly;
gettimeofday(&timeout, NULL);
msg->cookie = blkif->cookie;
DPRINTF("Generated cookie, %d\n",blkif->cookie);
/*Copy blk->params to msg*/
p = buf + sizeof(msg_hdr_t);
memcpy(p, path, strlen(path) + 1);
break;
case CTLMSG_NEWDEV:
DPRINTF("Write_msg called: CTLMSG_NEWDEV\n");
msglen = sizeof(msg_hdr_t) + sizeof(msg_newdev_t);
buf = malloc(msglen);
/*Assign header fields*/
msg = (msg_hdr_t *)buf;
msg->type = CTLMSG_NEWDEV;
msg->len = msglen;
msg->drivertype = blkif->drivertype;
msg->cookie = blkif->cookie;
msg_dev = (msg_newdev_t *)(buf + sizeof(msg_hdr_t));
msg_dev->devnum = blkif->minor;
msg_dev->domid = blkif->domid;
break;
case CTLMSG_CLOSE:
DPRINTF("Write_msg called: CTLMSG_CLOSE\n");
msglen = sizeof(msg_hdr_t);
buf = malloc(msglen);
/*Assign header fields*/
msg = (msg_hdr_t *)buf;
msg->type = CTLMSG_CLOSE;
msg->len = msglen;
msg->drivertype = blkif->drivertype;
msg->cookie = blkif->cookie;
break;
case CTLMSG_PID:
DPRINTF("Write_msg called: CTLMSG_PID\n");
msglen = sizeof(msg_hdr_t);
buf = malloc(msglen);
/*Assign header fields*/
msg = (msg_hdr_t *)buf;
msg->type = CTLMSG_PID;
msg->len = msglen;
msg->drivertype = blkif->drivertype;
msg->cookie = blkif->cookie;
break;
default:
return -1;
}
/*Now send the message*/
ret = 0;
FD_ZERO(&writefds);
FD_SET(fd,&writefds);
timeout.tv_sec = max_timeout; /*Wait for up to max_timeout seconds*/
timeout.tv_usec = 0;
if (select(fd+1, (fd_set *) 0, &writefds,
(fd_set *) 0, &timeout) > 0) {
len = write(fd, buf, msglen);
if (len == -1) DPRINTF("Write failed: (%d)\n",errno);
}
free(buf);
return len;
}
static int read_msg(int fd, int msgtype, void *ptr)
{
blkif_t *blkif;
blkif_info_t *blk;
msg_hdr_t *msg;
msg_pid_t *msg_pid;
char *p, *buf;
int msglen = MSG_SIZE, len, ret;
fd_set readfds;
struct timeval timeout;
image_t *image, *img;
blkif = (blkif_t *)ptr;
blk = blkif->info;
image = blkif->prv;
buf = malloc(MSG_SIZE);
ret = 0;
FD_ZERO(&readfds);
FD_SET(fd,&readfds);
timeout.tv_sec = max_timeout; /*Wait for up to max_timeout seconds*/
timeout.tv_usec = 0;
if (select(fd+1, &readfds, (fd_set *) 0,
(fd_set *) 0, &timeout) > 0) {
ret = read(fd, buf, msglen);
}
if (ret > 0) {
msg = (msg_hdr_t *)buf;
switch (msg->type)
{
case CTLMSG_IMG:
img = (image_t *)(buf + sizeof(msg_hdr_t));
image->size = img->size;
image->secsize = img->secsize;
image->info = img->info;
DPRINTF("Received CTLMSG_IMG: %llu, %lu, %u\n",
image->size, image->secsize, image->info);
if(msgtype != CTLMSG_IMG) ret = 0;
break;
case CTLMSG_IMG_FAIL:
DPRINTF("Received CTLMSG_IMG_FAIL, "
"unable to open image\n");
ret = 0;
break;
case CTLMSG_NEWDEV_RSP:
DPRINTF("Received CTLMSG_NEWDEV_RSP\n");
if(msgtype != CTLMSG_NEWDEV_RSP) ret = 0;
break;
case CTLMSG_NEWDEV_FAIL:
DPRINTF("Received CTLMSG_NEWDEV_FAIL\n");
ret = 0;
break;
case CTLMSG_CLOSE_RSP:
DPRINTF("Received CTLMSG_CLOSE_RSP\n");
if (msgtype != CTLMSG_CLOSE_RSP) ret = 0;
break;
case CTLMSG_PID_RSP:
DPRINTF("Received CTLMSG_PID_RSP\n");
if (msgtype != CTLMSG_PID_RSP) ret = 0;
else {
msg_pid = (msg_pid_t *)
(buf + sizeof(msg_hdr_t));
blkif->tappid = msg_pid->pid;
DPRINTF("\tPID: [%d]\n",blkif->tappid);
}
break;
default:
DPRINTF("UNKNOWN MESSAGE TYPE RECEIVED\n");
ret = 0;
break;
}
}
free(buf);
return ret;
}
int launch_tapdisk(char *wrctldev, char *rdctldev)
{
char *argv[] = { "tapdisk", wrctldev, rdctldev, NULL };
pid_t child;
if ((child = fork()) < 0)
return -1;
if (!child) {
int i;
for (i = 0 ; i < sysconf(_SC_OPEN_MAX) ; i++)
if (i != STDIN_FILENO &&
i != STDOUT_FILENO &&
i != STDERR_FILENO)
close(i);
execvp("tapdisk", argv);
_exit(1);
} else {
pid_t got;
do {
got = waitpid(child, NULL, 0);
} while (got != child);
}
return 0;
}
int blktapctrl_new_blkif(blkif_t *blkif)
{
blkif_info_t *blk;
int major, minor, fd_read, fd_write, type, new;
char *rdctldev, *wrctldev, *ptr;
image_t *image;
blkif_t *exist = NULL;
static uint16_t next_cookie = 0;
DPRINTF("Received a poll for a new vbd\n");
if ( ((blk=blkif->info) != NULL) && (blk->params != NULL) ) {
if (get_new_dev(&major, &minor, blkif)<0)
return -1;
if (test_path(blk->params, &ptr, &type, &exist) != 0) {
DPRINTF("Error in blktap device string(%s).\n",
blk->params);
return -1;
}
blkif->drivertype = type;
blkif->cookie = next_cookie++;
if (!exist) {
DPRINTF("Process does not exist:\n");
if (asprintf(&rdctldev,
"%s/tapctrlread%d", BLKTAP_CTRL_DIR, minor) == -1)
return -1;
if (asprintf(&wrctldev,
"%s/tapctrlwrite%d", BLKTAP_CTRL_DIR, minor) == -1) {
free(rdctldev);
return -1;
}
blkif->fds[READ] = open_ctrl_socket(rdctldev);
blkif->fds[WRITE] = open_ctrl_socket(wrctldev);
if (blkif->fds[READ] == -1 || blkif->fds[WRITE] == -1)
goto fail;
/*launch the new process*/
DPRINTF("Launching process, CMDLINE [tapdisk %s %s]\n",wrctldev, rdctldev);
if (launch_tapdisk(wrctldev, rdctldev) == -1) {
DPRINTF("Unable to fork, cmdline: [tapdisk %s %s]\n",wrctldev, rdctldev);
return -1;
}
free(rdctldev);
free(wrctldev);
} else {
DPRINTF("Process exists!\n");
blkif->fds[READ] = exist->fds[READ];
blkif->fds[WRITE] = exist->fds[WRITE];
}
add_disktype(blkif, type);
blkif->major = major;
blkif->minor = minor;
image = (image_t *)malloc(sizeof(image_t));
blkif->prv = (void *)image;
blkif->ops = &tapdisk_ops;
/*Retrieve the PID of the new process*/
if (get_tapdisk_pid(blkif) <= 0) {
DPRINTF("Unable to contact disk process\n");
goto fail;
}
/* Both of the following read and write calls will block up to
* max_timeout val*/
if (write_msg(blkif->fds[WRITE], CTLMSG_PARAMS, blkif, ptr)
<= 0) {
DPRINTF("Write_msg failed - CTLMSG_PARAMS\n");
goto fail;
}
if (read_msg(blkif->fds[READ], CTLMSG_IMG, blkif) <= 0) {
DPRINTF("Read_msg failure - CTLMSG_IMG\n");
goto fail;
}
} else return -1;
return 0;
fail:
ioctl(ctlfd, BLKTAP_IOCTL_FREEINTF, minor);
return -EINVAL;
}
int map_new_blktapctrl(blkif_t *blkif)
{
DPRINTF("Received a poll for a new devmap\n");
if (write_msg(blkif->fds[WRITE], CTLMSG_NEWDEV, blkif, NULL) <= 0) {
DPRINTF("Write_msg failed - CTLMSG_NEWDEV\n");
return -EINVAL;
}
if (read_msg(blkif->fds[READ], CTLMSG_NEWDEV_RSP, blkif) <= 0) {
DPRINTF("Read_msg failed - CTLMSG_NEWDEV_RSP\n");
return -EINVAL;
}
DPRINTF("Exiting map_new_blktapctrl\n");
return blkif->minor - 1;
}
int unmap_blktapctrl(blkif_t *blkif)
{
DPRINTF("Unmapping vbd\n");
if (write_msg(blkif->fds[WRITE], CTLMSG_CLOSE, blkif, NULL) <= 0) {
DPRINTF("Write_msg failed - CTLMSG_CLOSE\n");
return -EINVAL;
}
if (del_disktype(blkif)) {
close(blkif->fds[WRITE]);
close(blkif->fds[READ]);
}
return 0;
}
int open_ctrl_socket(char *devname)
{
int ret;
int ipc_fd;
fd_set socks;
struct timeval timeout;
if (mkdir(BLKTAP_CTRL_DIR, 0755) == 0)
DPRINTF("Created %s directory\n", BLKTAP_CTRL_DIR);
ret = mkfifo(devname,S_IRWXU|S_IRWXG|S_IRWXO);
if ( (ret != 0) && (errno != EEXIST) ) {
DPRINTF("ERROR: pipe failed (%d)\n", errno);
exit(0);
}
ipc_fd = open(devname,O_RDWR|O_NONBLOCK);
if (ipc_fd < 0) {
DPRINTF("FD open failed\n");
return -1;
}
return ipc_fd;
}
static void print_drivers(void)
{
int i, size;
size = sizeof(dtypes)/sizeof(disk_info_t *);
DPRINTF("blktapctrl: v1.0.0\n");
for (i = 0; i < size; i++)
DPRINTF("Found driver: [%s]\n",dtypes[i]->name);
}
static void write_pidfile(long pid)
{
char buf[100];
int len;
int fd;
int flags;
fd = open(PIDFILE, O_RDWR | O_CREAT, 0600);
if (fd == -1) {
DPRINTF("Opening pid file failed (%d)\n", errno);
exit(1);
}
/* We exit silently if daemon already running. */
if (lockf(fd, F_TLOCK, 0) == -1)
exit(0);
/* Set FD_CLOEXEC, so that tapdisk doesn't get this file
descriptor. */
if ((flags = fcntl(fd, F_GETFD)) == -1) {
DPRINTF("F_GETFD failed (%d)\n", errno);
exit(1);
}
flags |= FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == -1) {
DPRINTF("F_SETFD failed (%d)\n", errno);
exit(1);
}
len = sprintf(buf, "%ld\n", pid);
if (write(fd, buf, len) != len) {
DPRINTF("Writing pid file failed (%d)\n", errno);
exit(1);
}
}
int main(int argc, char *argv[])
{
char *devname;
tapdev_info_t *ctlinfo;
int tap_pfd, store_pfd, xs_fd, ret, timeout, pfd_count, count=0;
struct xs_handle *h;
struct pollfd pfd[NUM_POLL_FDS];
pid_t process;
char buf[128];
__init_blkif();
snprintf(buf, sizeof(buf), "BLKTAPCTRL[%d]", getpid());
openlog(buf, LOG_CONS|LOG_ODELAY, LOG_DAEMON);
if (daemon(0,0)) {
DPRINTF("daemon failed (%d)\n", errno);
goto open_failed;
}
print_drivers();
init_driver_list();
init_rng();
register_new_blkif_hook(blktapctrl_new_blkif);
register_new_devmap_hook(map_new_blktapctrl);
register_new_unmap_hook(unmap_blktapctrl);
/* Attach to blktap0 */
if (asprintf(&devname,"%s/%s0", BLKTAP_DEV_DIR, BLKTAP_DEV_NAME) == -1)
goto open_failed;
if ((ret = xc_find_device_number("blktap0")) < 0) {
DPRINTF("couldn't find device number for 'blktap0'\n");
goto open_failed;
}
blktap_major = major(ret);
make_blktap_dev(devname,blktap_major,0);
ctlfd = open(devname, O_RDWR);
if (ctlfd == -1) {
DPRINTF("blktap0 open failed\n");
goto open_failed;
}
retry:
/* Set up store connection and watch. */
h = xs_daemon_open();
if (h == NULL) {
DPRINTF("xs_daemon_open failed -- "
"is xenstore running?\n");
if (count < MAX_ATTEMPTS) {
count++;
sleep(2);
goto retry;
} else goto open_failed;
}
ret = setup_probe_watch(h);
if (ret != 0) {
DPRINTF("Failed adding device probewatch\n");
xs_daemon_close(h);
goto open_failed;
}
ioctl(ctlfd, BLKTAP_IOCTL_SETMODE, BLKTAP_MODE_INTERPOSE );
process = getpid();
write_pidfile(process);
ret = ioctl(ctlfd, BLKTAP_IOCTL_SENDPID, process );
/*Static pollhooks*/
pfd_count = 0;
tap_pfd = pfd_count++;
pfd[tap_pfd].fd = ctlfd;
pfd[tap_pfd].events = POLLIN;
store_pfd = pfd_count++;
pfd[store_pfd].fd = xs_fileno(h);
pfd[store_pfd].events = POLLIN;
while (run) {
timeout = 1000; /*Milliseconds*/
ret = poll(pfd, pfd_count, timeout);
if (ret > 0) {
if (pfd[store_pfd].revents) {
ret = xs_fire_next_watch(h);
}
}
}
xs_daemon_close(h);
ioctl(ctlfd, BLKTAP_IOCTL_SETMODE, BLKTAP_MODE_PASSTHROUGH );
close(ctlfd);
closelog();
return 0;
open_failed:
DPRINTF("Unable to start blktapctrl\n");
closelog();
return -1;
}
/*
* Local variables:
* c-file-style: "linux"
* indent-tabs-mode: t
* c-indent-level: 8
* c-basic-offset: 8
* tab-width: 8
* End:
*/