2008-08-13 10:25:34 +00:00
|
|
|
/*
|
2011-03-09 03:13:18 +00:00
|
|
|
* Copyright (C) 2010-2011 Red Hat, Inc.
|
|
|
|
* Copyright IBM Corp. 2008
|
2008-08-13 10:25:34 +00:00
|
|
|
*
|
|
|
|
* lxc_controller.c: linux container process controller
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* David L. Leskovec <dlesko at linux.vnet.ibm.com>
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library 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
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
2012-07-21 10:06:23 +00:00
|
|
|
* License along with this library; If not, see
|
|
|
|
* <http://www.gnu.org/licenses/>.
|
2008-08-13 10:25:34 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include <sys/epoll.h>
|
2008-08-13 10:52:15 +00:00
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <sys/socket.h>
|
2008-08-20 20:55:32 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/un.h>
|
2011-02-23 17:17:53 +00:00
|
|
|
#include <sys/utsname.h>
|
|
|
|
#include <sys/personality.h>
|
2008-08-13 10:25:34 +00:00
|
|
|
#include <unistd.h>
|
2008-08-13 10:52:15 +00:00
|
|
|
#include <paths.h>
|
2011-02-22 17:35:06 +00:00
|
|
|
#include <errno.h>
|
2008-08-13 10:52:15 +00:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <signal.h>
|
2008-08-20 20:55:32 +00:00
|
|
|
#include <getopt.h>
|
2009-04-22 14:26:50 +00:00
|
|
|
#include <sys/mount.h>
|
2010-11-16 19:01:37 +00:00
|
|
|
#include <locale.h>
|
2011-10-19 01:39:57 +00:00
|
|
|
#include <grp.h>
|
|
|
|
#include <sys/stat.h>
|
Initialize random generator in lxc controller
The lxc contoller eventually makes use of virRandomBits(), which was
segfaulting since virRandomInitialize() is never invoked.
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff554d560 in random_r () from /lib64/libc.so.6
(gdb) bt
0 0x00007ffff554d560 in random_r () from /lib64/libc.so.6
1 0x0000000000469eaa in virRandomBits (nbits=32) at util/virrandom.c:80
2 0x000000000045bf69 in virHashCreateFull (size=256,
dataFree=0x4aa2a2 <hashDataFree>, keyCode=0x45bd40 <virHashStrCode>,
keyEqual=0x45bdad <virHashStrEqual>, keyCopy=0x45bdfa <virHashStrCopy>,
keyFree=0x45be37 <virHashStrFree>) at util/virhash.c:134
3 0x000000000045c069 in virHashCreate (size=0, dataFree=0x4aa2a2 <hashDataFree>)
at util/virhash.c:164
4 0x00000000004aa562 in virNWFilterHashTableCreate (n=0)
at conf/nwfilter_params.c:686
5 0x00000000004aa95b in virNWFilterParseParamAttributes (cur=0x711d30)
at conf/nwfilter_params.c:793
6 0x0000000000481a7f in virDomainNetDefParseXML (caps=0x702c90, node=0x7116b0,
ctxt=0x7101b0, bootMap=0x0, flags=0) at conf/domain_conf.c:4589
7 0x000000000048cc36 in virDomainDefParseXML (caps=0x702c90, xml=0x710040,
root=0x7103b0, ctxt=0x7101b0, expectedVirtTypes=16, flags=0)
at conf/domain_conf.c:8658
8 0x000000000048f011 in virDomainDefParseNode (caps=0x702c90, xml=0x710040,
root=0x7103b0, expectedVirtTypes=16, flags=0) at conf/domain_conf.c:9360
9 0x000000000048ee30 in virDomainDefParse (xmlStr=0x0,
filename=0x702ae0 "/var/run/libvirt/lxc/x.xml", caps=0x702c90,
expectedVirtTypes=16, flags=0) at conf/domain_conf.c:9310
10 0x000000000048ef00 in virDomainDefParseFile (caps=0x702c90,
filename=0x702ae0 "/var/run/libvirt/lxc/x.xml", expectedVirtTypes=16, flags=0)
at conf/domain_conf.c:9332
11 0x0000000000425053 in main (argc=5, argv=0x7fffffffe2b8)
at lxc/lxc_controller.c:1773
2012-06-21 05:28:09 +00:00
|
|
|
#include <time.h>
|
2008-08-13 10:25:34 +00:00
|
|
|
|
2009-06-29 17:09:42 +00:00
|
|
|
#if HAVE_CAPNG
|
2010-03-09 18:22:22 +00:00
|
|
|
# include <cap-ng.h>
|
2009-06-29 17:09:42 +00:00
|
|
|
#endif
|
|
|
|
|
2011-11-10 11:51:32 +00:00
|
|
|
#if HAVE_NUMACTL
|
|
|
|
# define NUMA_VERSION1_COMPATIBILITY 1
|
|
|
|
# include <numa.h>
|
|
|
|
#endif
|
|
|
|
|
2008-11-04 22:30:33 +00:00
|
|
|
#include "virterror_internal.h"
|
2008-11-06 16:36:07 +00:00
|
|
|
#include "logging.h"
|
2008-08-13 10:25:34 +00:00
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
#include "lxc_conf.h"
|
2008-08-13 10:52:15 +00:00
|
|
|
#include "lxc_container.h"
|
2012-07-13 11:21:27 +00:00
|
|
|
#include "lxc_cgroup.h"
|
2011-11-02 16:03:09 +00:00
|
|
|
#include "virnetdev.h"
|
|
|
|
#include "virnetdevveth.h"
|
2008-08-13 10:52:15 +00:00
|
|
|
#include "memory.h"
|
|
|
|
#include "util.h"
|
2011-07-19 18:32:58 +00:00
|
|
|
#include "virfile.h"
|
2011-08-05 13:13:12 +00:00
|
|
|
#include "virpidfile.h"
|
2011-10-21 17:09:23 +00:00
|
|
|
#include "command.h"
|
2011-11-10 11:54:53 +00:00
|
|
|
#include "processinfo.h"
|
|
|
|
#include "nodeinfo.h"
|
Initialize random generator in lxc controller
The lxc contoller eventually makes use of virRandomBits(), which was
segfaulting since virRandomInitialize() is never invoked.
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff554d560 in random_r () from /lib64/libc.so.6
(gdb) bt
0 0x00007ffff554d560 in random_r () from /lib64/libc.so.6
1 0x0000000000469eaa in virRandomBits (nbits=32) at util/virrandom.c:80
2 0x000000000045bf69 in virHashCreateFull (size=256,
dataFree=0x4aa2a2 <hashDataFree>, keyCode=0x45bd40 <virHashStrCode>,
keyEqual=0x45bdad <virHashStrEqual>, keyCopy=0x45bdfa <virHashStrCopy>,
keyFree=0x45be37 <virHashStrFree>) at util/virhash.c:134
3 0x000000000045c069 in virHashCreate (size=0, dataFree=0x4aa2a2 <hashDataFree>)
at util/virhash.c:164
4 0x00000000004aa562 in virNWFilterHashTableCreate (n=0)
at conf/nwfilter_params.c:686
5 0x00000000004aa95b in virNWFilterParseParamAttributes (cur=0x711d30)
at conf/nwfilter_params.c:793
6 0x0000000000481a7f in virDomainNetDefParseXML (caps=0x702c90, node=0x7116b0,
ctxt=0x7101b0, bootMap=0x0, flags=0) at conf/domain_conf.c:4589
7 0x000000000048cc36 in virDomainDefParseXML (caps=0x702c90, xml=0x710040,
root=0x7103b0, ctxt=0x7101b0, expectedVirtTypes=16, flags=0)
at conf/domain_conf.c:8658
8 0x000000000048f011 in virDomainDefParseNode (caps=0x702c90, xml=0x710040,
root=0x7103b0, expectedVirtTypes=16, flags=0) at conf/domain_conf.c:9360
9 0x000000000048ee30 in virDomainDefParse (xmlStr=0x0,
filename=0x702ae0 "/var/run/libvirt/lxc/x.xml", caps=0x702c90,
expectedVirtTypes=16, flags=0) at conf/domain_conf.c:9310
10 0x000000000048ef00 in virDomainDefParseFile (caps=0x702c90,
filename=0x702ae0 "/var/run/libvirt/lxc/x.xml", expectedVirtTypes=16, flags=0)
at conf/domain_conf.c:9332
11 0x0000000000425053 in main (argc=5, argv=0x7fffffffe2b8)
at lxc/lxc_controller.c:1773
2012-06-21 05:28:09 +00:00
|
|
|
#include "virrandom.h"
|
2012-07-03 14:25:30 +00:00
|
|
|
#include "rpc/virnetserver.h"
|
2008-08-13 10:25:34 +00:00
|
|
|
|
2009-01-20 17:13:33 +00:00
|
|
|
#define VIR_FROM_THIS VIR_FROM_LXC
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
typedef struct _virLXCControllerConsole virLXCControllerConsole;
|
|
|
|
typedef virLXCControllerConsole *virLXCControllerConsolePtr;
|
|
|
|
struct _virLXCControllerConsole {
|
|
|
|
int hostWatch;
|
|
|
|
int hostFd; /* PTY FD in the host OS */
|
|
|
|
bool hostClosed;
|
|
|
|
int hostEpoll;
|
|
|
|
bool hostBlocking;
|
|
|
|
|
|
|
|
int contWatch;
|
|
|
|
int contFd; /* PTY FD in the container */
|
|
|
|
bool contClosed;
|
|
|
|
int contEpoll;
|
|
|
|
bool contBlocking;
|
|
|
|
|
|
|
|
int epollWatch;
|
|
|
|
int epollFd; /* epoll FD for dealing with EOF */
|
|
|
|
|
|
|
|
size_t fromHostLen;
|
|
|
|
char fromHostBuf[1024];
|
|
|
|
size_t fromContLen;
|
|
|
|
char fromContBuf[1024];
|
|
|
|
};
|
|
|
|
|
2012-07-03 10:54:09 +00:00
|
|
|
typedef struct _virLXCController virLXCController;
|
|
|
|
typedef virLXCController *virLXCControllerPtr;
|
|
|
|
struct _virLXCController {
|
|
|
|
char *name;
|
|
|
|
virDomainDefPtr def;
|
2012-07-03 11:06:38 +00:00
|
|
|
|
2012-07-03 12:16:48 +00:00
|
|
|
int handshakeFd;
|
|
|
|
|
2012-07-03 11:12:59 +00:00
|
|
|
pid_t initpid;
|
|
|
|
|
2012-07-03 11:06:38 +00:00
|
|
|
size_t nveths;
|
|
|
|
char **veths;
|
2012-07-03 11:42:53 +00:00
|
|
|
|
|
|
|
size_t nconsoles;
|
|
|
|
virLXCControllerConsolePtr consoles;
|
2012-07-03 13:02:33 +00:00
|
|
|
char *devptmx;
|
2012-07-03 12:49:19 +00:00
|
|
|
|
|
|
|
size_t nloopDevs;
|
|
|
|
int *loopDevFds;
|
2012-07-03 12:59:36 +00:00
|
|
|
|
|
|
|
virSecurityManagerPtr securityManager;
|
2012-07-03 13:40:45 +00:00
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
/* Server socket */
|
|
|
|
virNetServerPtr server;
|
2012-07-03 10:54:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void virLXCControllerFree(virLXCControllerPtr ctrl);
|
|
|
|
|
|
|
|
static virLXCControllerPtr virLXCControllerNew(const char *name)
|
|
|
|
{
|
|
|
|
virLXCControllerPtr ctrl = NULL;
|
|
|
|
virCapsPtr caps = NULL;
|
|
|
|
char *configFile = NULL;
|
|
|
|
|
|
|
|
if (VIR_ALLOC(ctrl) < 0)
|
|
|
|
goto no_memory;
|
|
|
|
|
|
|
|
if (!(ctrl->name = strdup(name)))
|
|
|
|
goto no_memory;
|
|
|
|
|
|
|
|
if ((caps = lxcCapsInit(NULL)) == NULL)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if ((configFile = virDomainConfigFile(LXC_STATE_DIR,
|
|
|
|
ctrl->name)) == NULL)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if ((ctrl->def = virDomainDefParseFile(caps,
|
|
|
|
configFile,
|
|
|
|
1 << VIR_DOMAIN_VIRT_LXC,
|
|
|
|
0)) == NULL)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
VIR_FREE(configFile);
|
|
|
|
virCapabilitiesFree(caps);
|
|
|
|
return ctrl;
|
|
|
|
|
|
|
|
no_memory:
|
|
|
|
virReportOOMError();
|
|
|
|
error:
|
|
|
|
virLXCControllerFree(ctrl);
|
|
|
|
ctrl = NULL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2012-07-03 11:12:59 +00:00
|
|
|
|
2012-07-03 12:49:19 +00:00
|
|
|
static int virLXCControllerCloseLoopDevices(virLXCControllerPtr ctrl,
|
|
|
|
bool force)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0 ; i < ctrl->nloopDevs ; i++) {
|
|
|
|
if (force) {
|
|
|
|
VIR_FORCE_CLOSE(ctrl->loopDevFds[i]);
|
|
|
|
} else {
|
|
|
|
if (VIR_CLOSE(ctrl->loopDevFds[i]) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to close loop device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 11:12:59 +00:00
|
|
|
static void virLXCControllerStopInit(virLXCControllerPtr ctrl)
|
|
|
|
{
|
|
|
|
if (ctrl->initpid == 0)
|
|
|
|
return;
|
|
|
|
|
2012-07-03 12:49:19 +00:00
|
|
|
virLXCControllerCloseLoopDevices(ctrl, true);
|
2012-07-03 11:12:59 +00:00
|
|
|
virPidAbort(ctrl->initpid);
|
|
|
|
ctrl->initpid = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
static void virLXCControllerConsoleClose(virLXCControllerConsolePtr console)
|
|
|
|
{
|
|
|
|
if (console->hostWatch != -1)
|
|
|
|
virEventRemoveHandle(console->hostWatch);
|
|
|
|
VIR_FORCE_CLOSE(console->hostFd);
|
|
|
|
|
|
|
|
if (console->contWatch != -1)
|
|
|
|
virEventRemoveHandle(console->contWatch);
|
|
|
|
VIR_FORCE_CLOSE(console->contFd);
|
|
|
|
|
|
|
|
if (console->epollWatch != -1)
|
|
|
|
virEventRemoveHandle(console->epollWatch);
|
|
|
|
VIR_FORCE_CLOSE(console->epollFd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 10:54:09 +00:00
|
|
|
static void virLXCControllerFree(virLXCControllerPtr ctrl)
|
|
|
|
{
|
2012-07-03 11:06:38 +00:00
|
|
|
size_t i;
|
|
|
|
|
2012-07-03 10:54:09 +00:00
|
|
|
if (!ctrl)
|
|
|
|
return;
|
|
|
|
|
2012-07-03 11:12:59 +00:00
|
|
|
virLXCControllerStopInit(ctrl);
|
|
|
|
|
2012-07-03 12:59:36 +00:00
|
|
|
virSecurityManagerFree(ctrl->securityManager);
|
|
|
|
|
2012-07-03 11:06:38 +00:00
|
|
|
for (i = 0 ; i < ctrl->nveths ; i++)
|
|
|
|
VIR_FREE(ctrl->veths[i]);
|
|
|
|
VIR_FREE(ctrl->veths);
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
for (i = 0 ; i < ctrl->nconsoles ; i++)
|
|
|
|
virLXCControllerConsoleClose(&(ctrl->consoles[i]));
|
|
|
|
VIR_FREE(ctrl->consoles);
|
|
|
|
|
2012-07-03 12:16:48 +00:00
|
|
|
VIR_FORCE_CLOSE(ctrl->handshakeFd);
|
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
VIR_FREE(ctrl->devptmx);
|
|
|
|
|
2012-07-03 10:54:09 +00:00
|
|
|
virDomainDefFree(ctrl->def);
|
|
|
|
VIR_FREE(ctrl->name);
|
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
virNetServerFree(ctrl->server);
|
2012-07-03 13:40:45 +00:00
|
|
|
|
2012-07-03 10:54:09 +00:00
|
|
|
VIR_FREE(ctrl);
|
|
|
|
}
|
|
|
|
|
2012-07-03 11:06:38 +00:00
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
static int virLXCControllerAddConsole(virLXCControllerPtr ctrl,
|
|
|
|
int hostFd)
|
|
|
|
{
|
|
|
|
if (VIR_EXPAND_N(ctrl->consoles, ctrl->nconsoles, 1) < 0) {
|
|
|
|
virReportOOMError();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
ctrl->consoles[ctrl->nconsoles-1].hostFd = hostFd;
|
|
|
|
ctrl->consoles[ctrl->nconsoles-1].hostWatch = -1;
|
|
|
|
|
|
|
|
ctrl->consoles[ctrl->nconsoles-1].contFd = -1;
|
|
|
|
ctrl->consoles[ctrl->nconsoles-1].contWatch = -1;
|
|
|
|
|
|
|
|
ctrl->consoles[ctrl->nconsoles-1].epollFd = -1;
|
|
|
|
ctrl->consoles[ctrl->nconsoles-1].epollWatch = -1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int virLXCControllerConsoleSetNonblocking(virLXCControllerConsolePtr console)
|
|
|
|
{
|
|
|
|
if (virSetBlocking(console->hostFd, false) < 0 ||
|
|
|
|
virSetBlocking(console->contFd, false) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to set console file descriptor non-blocking"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 12:16:48 +00:00
|
|
|
static int virLXCControllerDaemonHandshake(virLXCControllerPtr ctrl)
|
|
|
|
{
|
|
|
|
if (lxcContainerSendContinue(ctrl->handshakeFd) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("error sending continue signal to daemon"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
VIR_FORCE_CLOSE(ctrl->handshakeFd);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 11:06:38 +00:00
|
|
|
static int virLXCControllerValidateNICs(virLXCControllerPtr ctrl)
|
|
|
|
{
|
|
|
|
if (ctrl->def->nnets != ctrl->nveths) {
|
|
|
|
lxcError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("expecting %d veths, but got %zu"),
|
|
|
|
ctrl->def->nnets, ctrl->nveths);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
static int virLXCControllerValidateConsoles(virLXCControllerPtr ctrl)
|
|
|
|
{
|
|
|
|
if (ctrl->def->nconsoles != ctrl->nconsoles) {
|
|
|
|
lxcError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("expecting %d consoles, but got %zu tty file handlers"),
|
|
|
|
ctrl->def->nconsoles, ctrl->nconsoles);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 14:06:27 +00:00
|
|
|
static int virLXCControllerSetupLoopDevice(virDomainFSDefPtr fs)
|
2011-08-04 09:13:02 +00:00
|
|
|
{
|
2012-07-03 14:06:27 +00:00
|
|
|
int lofd;
|
2011-08-04 09:13:02 +00:00
|
|
|
char *loname = NULL;
|
|
|
|
|
2012-07-03 14:06:27 +00:00
|
|
|
if ((lofd = virFileLoopDeviceAssociate(fs->src, &loname)) < 0)
|
2011-08-04 09:13:02 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We now change it into a block device type, so that
|
|
|
|
* the rest of container setup 'just works'
|
|
|
|
*/
|
|
|
|
fs->type = VIR_DOMAIN_FS_TYPE_BLOCK;
|
|
|
|
VIR_FREE(fs->src);
|
|
|
|
fs->src = loname;
|
|
|
|
loname = NULL;
|
|
|
|
|
|
|
|
return lofd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 12:49:19 +00:00
|
|
|
static int virLXCControllerSetupLoopDevices(virLXCControllerPtr ctrl)
|
2011-08-04 09:13:02 +00:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
int ret = -1;
|
|
|
|
|
2012-07-03 12:49:19 +00:00
|
|
|
for (i = 0 ; i < ctrl->def->nfss ; i++) {
|
2011-08-04 09:13:02 +00:00
|
|
|
int fd;
|
|
|
|
|
2012-07-03 12:49:19 +00:00
|
|
|
if (ctrl->def->fss[i]->type != VIR_DOMAIN_FS_TYPE_FILE)
|
2011-08-04 09:13:02 +00:00
|
|
|
continue;
|
|
|
|
|
2012-07-03 14:06:27 +00:00
|
|
|
fd = virLXCControllerSetupLoopDevice(ctrl->def->fss[i]);
|
2011-08-04 09:13:02 +00:00
|
|
|
if (fd < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
VIR_DEBUG("Saving loop fd %d", fd);
|
2012-07-03 12:49:19 +00:00
|
|
|
if (VIR_EXPAND_N(ctrl->loopDevFds, ctrl->nloopDevs, 1) < 0) {
|
2011-08-04 09:13:02 +00:00
|
|
|
VIR_FORCE_CLOSE(fd);
|
|
|
|
virReportOOMError();
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2012-07-03 12:49:19 +00:00
|
|
|
ctrl->loopDevFds[ctrl->nloopDevs - 1] = fd;
|
2011-08-04 09:13:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
VIR_DEBUG("Setup all loop devices");
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-11-10 11:51:32 +00:00
|
|
|
#if HAVE_NUMACTL
|
2012-07-03 13:53:59 +00:00
|
|
|
static int virLXCControllerSetupNUMAPolicy(virLXCControllerPtr ctrl)
|
2011-11-10 11:51:32 +00:00
|
|
|
{
|
|
|
|
nodemask_t mask;
|
|
|
|
int mode = -1;
|
|
|
|
int node = -1;
|
|
|
|
int ret = -1;
|
|
|
|
int i = 0;
|
|
|
|
int maxnode = 0;
|
|
|
|
bool warned = false;
|
|
|
|
|
2012-07-03 13:53:59 +00:00
|
|
|
if (!ctrl->def->numatune.memory.nodemask)
|
2011-11-10 11:51:32 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
VIR_DEBUG("Setting NUMA memory policy");
|
|
|
|
|
|
|
|
if (numa_available() < 0) {
|
|
|
|
lxcError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
"%s", _("Host kernel is not aware of NUMA."));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
maxnode = numa_max_node() + 1;
|
|
|
|
|
|
|
|
/* Convert nodemask to NUMA bitmask. */
|
|
|
|
nodemask_zero(&mask);
|
|
|
|
for (i = 0; i < VIR_DOMAIN_CPUMASK_LEN; i++) {
|
2012-07-03 13:53:59 +00:00
|
|
|
if (ctrl->def->numatune.memory.nodemask[i]) {
|
2011-11-10 11:51:32 +00:00
|
|
|
if (i > NUMA_NUM_NODES) {
|
|
|
|
lxcError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("Host cannot support NUMA node %d"), i);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (i > maxnode && !warned) {
|
|
|
|
VIR_WARN("nodeset is out of range, there is only %d NUMA "
|
|
|
|
"nodes on host", maxnode);
|
|
|
|
warned = true;
|
|
|
|
}
|
|
|
|
nodemask_set(&mask, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-03 13:53:59 +00:00
|
|
|
mode = ctrl->def->numatune.memory.mode;
|
2011-11-10 11:51:32 +00:00
|
|
|
|
|
|
|
if (mode == VIR_DOMAIN_NUMATUNE_MEM_STRICT) {
|
|
|
|
numa_set_bind_policy(1);
|
|
|
|
numa_set_membind(&mask);
|
|
|
|
numa_set_bind_policy(0);
|
|
|
|
} else if (mode == VIR_DOMAIN_NUMATUNE_MEM_PREFERRED) {
|
|
|
|
int nnodes = 0;
|
|
|
|
for (i = 0; i < NUMA_NUM_NODES; i++) {
|
|
|
|
if (nodemask_isset(&mask, i)) {
|
|
|
|
node = i;
|
|
|
|
nnodes++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nnodes != 1) {
|
|
|
|
lxcError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
"%s", _("NUMA memory tuning in 'preferred' mode "
|
|
|
|
"only supports single node"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
numa_set_bind_policy(0);
|
|
|
|
numa_set_preferred(node);
|
|
|
|
} else if (mode == VIR_DOMAIN_NUMATUNE_MEM_INTERLEAVE) {
|
|
|
|
numa_set_interleave_mask(&mask);
|
|
|
|
} else {
|
|
|
|
lxcError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("Unable to set NUMA policy %s"),
|
|
|
|
virDomainNumatuneMemModeTypeToString(mode));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#else
|
2012-07-03 13:53:59 +00:00
|
|
|
static int virLXCControllerSetupNUMAPolicy(virLXCControllerPtr ctrl)
|
2011-11-10 11:51:32 +00:00
|
|
|
{
|
2012-07-03 13:53:59 +00:00
|
|
|
if (ctrl->def->numatune.memory.nodemask) {
|
2011-11-10 11:51:32 +00:00
|
|
|
lxcError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("NUMA policy is not available on this platform"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2011-11-10 11:54:53 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* To be run while still single threaded
|
|
|
|
*/
|
2012-07-03 13:53:59 +00:00
|
|
|
static int virLXCControllerSetupCpuAffinity(virLXCControllerPtr ctrl)
|
2011-11-10 11:54:53 +00:00
|
|
|
{
|
|
|
|
int i, hostcpus, maxcpu = CPU_SETSIZE;
|
|
|
|
virNodeInfo nodeinfo;
|
|
|
|
unsigned char *cpumap;
|
|
|
|
int cpumaplen;
|
|
|
|
|
|
|
|
VIR_DEBUG("Setting CPU affinity");
|
|
|
|
|
|
|
|
if (nodeGetInfo(NULL, &nodeinfo) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* setaffinity fails if you set bits for CPUs which
|
|
|
|
* aren't present, so we have to limit ourselves */
|
|
|
|
hostcpus = VIR_NODEINFO_MAXCPUS(nodeinfo);
|
|
|
|
if (maxcpu > hostcpus)
|
|
|
|
maxcpu = hostcpus;
|
|
|
|
|
|
|
|
cpumaplen = VIR_CPU_MAPLEN(maxcpu);
|
|
|
|
if (VIR_ALLOC_N(cpumap, cpumaplen) < 0) {
|
|
|
|
virReportOOMError();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-07-03 13:53:59 +00:00
|
|
|
if (ctrl->def->cpumask) {
|
2011-11-10 11:54:53 +00:00
|
|
|
/* XXX why don't we keep 'cpumask' in the libvirt cpumap
|
|
|
|
* format to start with ?!?! */
|
2012-07-03 13:53:59 +00:00
|
|
|
for (i = 0 ; i < maxcpu && i < ctrl->def->cpumasklen ; i++)
|
|
|
|
if (ctrl->def->cpumask[i])
|
2011-11-10 11:54:53 +00:00
|
|
|
VIR_USE_CPU(cpumap, i);
|
|
|
|
} else {
|
|
|
|
/* You may think this is redundant, but we can't assume libvirtd
|
|
|
|
* itself is running on all pCPUs, so we need to explicitly set
|
|
|
|
* the spawned LXC instance to all pCPUs if no map is given in
|
|
|
|
* its config file */
|
|
|
|
for (i = 0 ; i < maxcpu ; i++)
|
|
|
|
VIR_USE_CPU(cpumap, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We are pressuming we are running between fork/exec of LXC
|
|
|
|
* so use '0' to indicate our own process ID. No threads are
|
|
|
|
* running at this point
|
|
|
|
*/
|
|
|
|
if (virProcessInfoSetAffinity(0, /* Self */
|
|
|
|
cpumap, cpumaplen, maxcpu) < 0) {
|
|
|
|
VIR_FREE(cpumap);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
VIR_FREE(cpumap);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-11-10 12:38:24 +00:00
|
|
|
/**
|
2012-07-03 13:53:59 +00:00
|
|
|
* virLXCControllerSetupResourceLimits
|
|
|
|
* @ctrl: the controller state
|
2011-11-10 12:38:24 +00:00
|
|
|
*
|
|
|
|
* Creates a cgroup for the container, moves the task inside,
|
|
|
|
* and sets resource limits
|
|
|
|
*
|
|
|
|
* Returns 0 on success or -1 in case of error
|
|
|
|
*/
|
2012-07-03 13:53:59 +00:00
|
|
|
static int virLXCControllerSetupResourceLimits(virLXCControllerPtr ctrl)
|
2011-11-10 12:38:24 +00:00
|
|
|
{
|
|
|
|
|
2012-07-03 13:53:59 +00:00
|
|
|
if (virLXCControllerSetupCpuAffinity(ctrl) < 0)
|
2011-11-10 12:38:24 +00:00
|
|
|
return -1;
|
|
|
|
|
2012-07-03 13:53:59 +00:00
|
|
|
if (virLXCControllerSetupNUMAPolicy(ctrl) < 0)
|
2011-11-10 12:38:24 +00:00
|
|
|
return -1;
|
|
|
|
|
2012-07-13 11:21:27 +00:00
|
|
|
return virLXCCgroupSetup(ctrl->def);
|
2008-10-03 16:46:01 +00:00
|
|
|
}
|
|
|
|
|
2008-12-23 13:03:29 +00:00
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
static int virLXCControllerClientHook(virNetServerPtr server ATTRIBUTE_UNUSED,
|
|
|
|
virNetServerClientPtr client,
|
|
|
|
void *opaque)
|
|
|
|
{
|
|
|
|
virLXCControllerPtr ctrl = opaque;
|
|
|
|
virNetServerClientSetPrivateData(client, ctrl, NULL);
|
|
|
|
return 0;
|
2008-08-20 20:55:32 +00:00
|
|
|
}
|
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
|
|
|
|
static int virLXCControllerSetupServer(virLXCControllerPtr ctrl)
|
2008-08-20 20:55:32 +00:00
|
|
|
{
|
2012-07-03 14:25:30 +00:00
|
|
|
virNetServerServicePtr svc = NULL;
|
|
|
|
char *sockpath;
|
2008-08-20 20:55:32 +00:00
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
if (virAsprintf(&sockpath, "%s/%s.sock",
|
|
|
|
LXC_STATE_DIR, ctrl->name) < 0) {
|
|
|
|
virReportOOMError();
|
|
|
|
return -1;
|
2008-08-20 20:55:32 +00:00
|
|
|
}
|
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
if (!(ctrl->server = virNetServerNew(0, 0, 0, 1,
|
|
|
|
-1, 0, false,
|
|
|
|
NULL,
|
|
|
|
virLXCControllerClientHook,
|
|
|
|
ctrl)))
|
2009-08-03 12:37:44 +00:00
|
|
|
goto error;
|
2008-08-20 20:55:32 +00:00
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
if (!(svc = virNetServerServiceNewUNIX(sockpath,
|
|
|
|
0700,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
false,
|
|
|
|
5,
|
|
|
|
NULL)))
|
2008-08-20 20:55:32 +00:00
|
|
|
goto error;
|
2012-07-03 14:25:30 +00:00
|
|
|
|
|
|
|
if (virNetServerAddService(ctrl->server, svc, NULL) < 0)
|
2008-08-20 20:55:32 +00:00
|
|
|
goto error;
|
2012-07-03 14:25:30 +00:00
|
|
|
virNetServerServiceFree(svc);
|
|
|
|
svc = NULL;
|
2008-08-20 20:55:32 +00:00
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
virNetServerUpdateServices(ctrl->server, true);
|
|
|
|
VIR_FREE(sockpath);
|
|
|
|
return 0;
|
2008-08-20 20:55:32 +00:00
|
|
|
|
|
|
|
error:
|
2012-07-03 14:25:30 +00:00
|
|
|
VIR_FREE(sockpath);
|
|
|
|
virNetServerFree(ctrl->server);
|
|
|
|
ctrl->server = NULL;
|
|
|
|
virNetServerServiceFree(svc);
|
2008-08-20 20:55:32 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2008-08-13 10:25:34 +00:00
|
|
|
|
2009-06-29 17:09:42 +00:00
|
|
|
|
|
|
|
static int lxcControllerClearCapabilities(void)
|
|
|
|
{
|
|
|
|
#if HAVE_CAPNG
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
capng_clear(CAPNG_SELECT_BOTH);
|
|
|
|
|
|
|
|
if ((ret = capng_apply(CAPNG_SELECT_BOTH)) < 0) {
|
2010-02-09 18:22:56 +00:00
|
|
|
lxcError(VIR_ERR_INTERNAL_ERROR,
|
2009-06-29 17:09:42 +00:00
|
|
|
_("failed to apply capabilities: %d"), ret);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
#else
|
2011-05-09 09:24:09 +00:00
|
|
|
VIR_WARN("libcap-ng support not compiled in, unable to clear capabilities");
|
2009-06-29 17:09:42 +00:00
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-10-19 15:22:24 +00:00
|
|
|
static bool quit = false;
|
|
|
|
static virMutex lock;
|
|
|
|
|
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
static void virLXCControllerSignalChildIO(virNetServerPtr server ATTRIBUTE_UNUSED,
|
|
|
|
siginfo_t *info ATTRIBUTE_UNUSED,
|
2012-07-03 11:12:59 +00:00
|
|
|
void *opaque)
|
2011-02-22 17:35:06 +00:00
|
|
|
{
|
2012-07-03 11:12:59 +00:00
|
|
|
virLXCControllerPtr ctrl = opaque;
|
2012-07-03 14:25:30 +00:00
|
|
|
int ret;
|
2011-10-19 15:22:24 +00:00
|
|
|
|
|
|
|
ret = waitpid(-1, NULL, WNOHANG);
|
2012-07-09 13:55:31 +00:00
|
|
|
if (ret == ctrl->initpid) {
|
|
|
|
virMutexLock(&lock);
|
|
|
|
quit = true;
|
|
|
|
virMutexUnlock(&lock);
|
|
|
|
}
|
2011-10-19 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
static void virLXCControllerConsoleUpdateWatch(virLXCControllerConsolePtr console)
|
2011-10-19 15:22:24 +00:00
|
|
|
{
|
|
|
|
int hostEvents = 0;
|
|
|
|
int contEvents = 0;
|
|
|
|
|
2012-01-12 17:03:03 +00:00
|
|
|
if (!console->hostClosed || (!console->hostBlocking && console->fromContLen)) {
|
2011-10-19 15:22:24 +00:00
|
|
|
if (console->fromHostLen < sizeof(console->fromHostBuf))
|
|
|
|
hostEvents |= VIR_EVENT_HANDLE_READABLE;
|
|
|
|
if (console->fromContLen)
|
|
|
|
hostEvents |= VIR_EVENT_HANDLE_WRITABLE;
|
|
|
|
}
|
2012-01-12 17:03:03 +00:00
|
|
|
if (!console->contClosed || (!console->contBlocking && console->fromHostLen)) {
|
2011-10-19 15:22:24 +00:00
|
|
|
if (console->fromContLen < sizeof(console->fromContBuf))
|
|
|
|
contEvents |= VIR_EVENT_HANDLE_READABLE;
|
|
|
|
if (console->fromHostLen)
|
|
|
|
contEvents |= VIR_EVENT_HANDLE_WRITABLE;
|
|
|
|
}
|
|
|
|
|
2012-01-12 17:03:03 +00:00
|
|
|
VIR_DEBUG("Container watch %d=%d host watch %d=%d",
|
|
|
|
console->contWatch, contEvents,
|
|
|
|
console->hostWatch, hostEvents);
|
2011-10-19 15:22:24 +00:00
|
|
|
virEventUpdateHandle(console->contWatch, contEvents);
|
|
|
|
virEventUpdateHandle(console->hostWatch, hostEvents);
|
2011-02-22 17:35:06 +00:00
|
|
|
|
2012-01-12 17:03:03 +00:00
|
|
|
if (console->hostClosed) {
|
|
|
|
int events = EPOLLIN | EPOLLET;
|
|
|
|
if (console->hostBlocking)
|
|
|
|
events |= EPOLLOUT;
|
|
|
|
|
|
|
|
if (events != console->hostEpoll) {
|
|
|
|
struct epoll_event event;
|
|
|
|
int action = EPOLL_CTL_ADD;
|
|
|
|
if (console->hostEpoll)
|
|
|
|
action = EPOLL_CTL_MOD;
|
|
|
|
|
|
|
|
VIR_DEBUG("newHostEvents=%x oldHostEvents=%x", events, console->hostEpoll);
|
|
|
|
|
|
|
|
event.events = events;
|
|
|
|
event.data.fd = console->hostFd;
|
|
|
|
if (epoll_ctl(console->epollFd, action, console->hostFd, &event) < 0) {
|
|
|
|
VIR_DEBUG(":fail");
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to add epoll fd"));
|
|
|
|
quit = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
console->hostEpoll = events;
|
|
|
|
VIR_DEBUG("newHostEvents=%x oldHostEvents=%x", events, console->hostEpoll);
|
|
|
|
}
|
|
|
|
} else if (console->hostEpoll) {
|
|
|
|
VIR_DEBUG("Stop epoll oldContEvents=%x", console->hostEpoll);
|
|
|
|
if (epoll_ctl(console->epollFd, EPOLL_CTL_DEL, console->hostFd, NULL) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to remove epoll fd"));
|
|
|
|
VIR_DEBUG(":fail");
|
|
|
|
quit = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
console->hostEpoll = 0;
|
|
|
|
}
|
2011-10-19 15:22:24 +00:00
|
|
|
|
2012-01-12 17:03:03 +00:00
|
|
|
if (console->contClosed) {
|
|
|
|
int events = EPOLLIN | EPOLLET;
|
|
|
|
if (console->contBlocking)
|
|
|
|
events |= EPOLLOUT;
|
|
|
|
|
|
|
|
if (events != console->contEpoll) {
|
|
|
|
struct epoll_event event;
|
|
|
|
int action = EPOLL_CTL_ADD;
|
|
|
|
if (console->contEpoll)
|
|
|
|
action = EPOLL_CTL_MOD;
|
|
|
|
|
|
|
|
VIR_DEBUG("newContEvents=%x oldContEvents=%x", events, console->contEpoll);
|
|
|
|
|
|
|
|
event.events = events;
|
|
|
|
event.data.fd = console->contFd;
|
|
|
|
if (epoll_ctl(console->epollFd, action, console->contFd, &event) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to add epoll fd"));
|
|
|
|
VIR_DEBUG(":fail");
|
|
|
|
quit = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
console->contEpoll = events;
|
|
|
|
VIR_DEBUG("newHostEvents=%x oldHostEvents=%x", events, console->contEpoll);
|
|
|
|
}
|
|
|
|
} else if (console->contEpoll) {
|
|
|
|
VIR_DEBUG("Stop epoll oldContEvents=%x", console->contEpoll);
|
|
|
|
if (epoll_ctl(console->epollFd, EPOLL_CTL_DEL, console->contFd, NULL) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to remove epoll fd"));
|
|
|
|
VIR_DEBUG(":fail");
|
|
|
|
quit = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
console->contEpoll = 0;
|
|
|
|
}
|
|
|
|
cleanup:
|
|
|
|
return;
|
|
|
|
}
|
2011-10-19 15:22:24 +00:00
|
|
|
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
static void virLXCControllerConsoleEPoll(int watch, int fd, int events, void *opaque)
|
2011-10-19 15:22:24 +00:00
|
|
|
{
|
2012-07-03 11:42:53 +00:00
|
|
|
virLXCControllerConsolePtr console = opaque;
|
2011-10-19 15:22:24 +00:00
|
|
|
|
2012-01-12 17:03:03 +00:00
|
|
|
virMutexLock(&lock);
|
|
|
|
VIR_DEBUG("IO event watch=%d fd=%d events=%d fromHost=%zu fromcont=%zu",
|
|
|
|
watch, fd, events,
|
|
|
|
console->fromHostLen,
|
|
|
|
console->fromContLen);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
struct epoll_event event;
|
|
|
|
int ret;
|
|
|
|
ret = epoll_wait(console->epollFd, &event, 1, 0);
|
2011-10-19 15:22:24 +00:00
|
|
|
if (ret < 0) {
|
2012-04-27 18:39:17 +00:00
|
|
|
if (errno == EINTR)
|
2011-10-19 15:22:24 +00:00
|
|
|
continue;
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to wait on epoll"));
|
|
|
|
quit = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2012-01-12 17:03:03 +00:00
|
|
|
if (ret == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
VIR_DEBUG("fd=%d hostFd=%d contFd=%d hostEpoll=%x contEpoll=%x",
|
|
|
|
event.data.fd, console->hostFd, console->contFd,
|
|
|
|
console->hostEpoll, console->contEpoll);
|
|
|
|
|
2011-10-19 15:22:24 +00:00
|
|
|
/* If we get HUP+dead PID, we just re-enable the main loop
|
|
|
|
* which will see the PID has died and exit */
|
|
|
|
if ((event.events & EPOLLIN)) {
|
2012-01-12 17:03:03 +00:00
|
|
|
if (event.data.fd == console->hostFd) {
|
|
|
|
console->hostClosed = false;
|
2011-10-19 15:22:24 +00:00
|
|
|
} else {
|
2012-01-12 17:03:03 +00:00
|
|
|
console->contClosed = false;
|
2011-10-19 15:22:24 +00:00
|
|
|
}
|
2012-07-03 11:42:53 +00:00
|
|
|
virLXCControllerConsoleUpdateWatch(console);
|
2011-10-19 15:22:24 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
2012-01-12 17:03:03 +00:00
|
|
|
virMutexUnlock(&lock);
|
2011-10-19 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
static void virLXCControllerConsoleIO(int watch, int fd, int events, void *opaque)
|
2011-10-19 15:22:24 +00:00
|
|
|
{
|
2012-07-03 11:42:53 +00:00
|
|
|
virLXCControllerConsolePtr console = opaque;
|
2011-10-19 15:22:24 +00:00
|
|
|
|
|
|
|
virMutexLock(&lock);
|
2012-01-12 17:03:03 +00:00
|
|
|
VIR_DEBUG("IO event watch=%d fd=%d events=%d fromHost=%zu fromcont=%zu",
|
|
|
|
watch, fd, events,
|
|
|
|
console->fromHostLen,
|
|
|
|
console->fromContLen);
|
2011-10-19 15:22:24 +00:00
|
|
|
if (events & VIR_EVENT_HANDLE_READABLE) {
|
|
|
|
char *buf;
|
|
|
|
size_t *len;
|
|
|
|
size_t avail;
|
|
|
|
ssize_t done;
|
|
|
|
if (watch == console->hostWatch) {
|
|
|
|
buf = console->fromHostBuf;
|
|
|
|
len = &console->fromHostLen;
|
|
|
|
avail = sizeof(console->fromHostBuf) - *len;
|
|
|
|
} else {
|
|
|
|
buf = console->fromContBuf;
|
|
|
|
len = &console->fromContLen;
|
|
|
|
avail = sizeof(console->fromContBuf) - *len;
|
|
|
|
}
|
|
|
|
reread:
|
|
|
|
done = read(fd, buf + *len, avail);
|
|
|
|
if (done == -1 && errno == EINTR)
|
|
|
|
goto reread;
|
|
|
|
if (done == -1 && errno != EAGAIN) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to read container pty"));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
if (done > 0) {
|
|
|
|
*len += done;
|
|
|
|
} else {
|
|
|
|
VIR_DEBUG("Read fd %d done %d errno %d", fd, (int)done, errno);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (events & VIR_EVENT_HANDLE_WRITABLE) {
|
|
|
|
char *buf;
|
|
|
|
size_t *len;
|
|
|
|
ssize_t done;
|
|
|
|
if (watch == console->hostWatch) {
|
|
|
|
buf = console->fromContBuf;
|
|
|
|
len = &console->fromContLen;
|
|
|
|
} else {
|
|
|
|
buf = console->fromHostBuf;
|
|
|
|
len = &console->fromHostLen;
|
|
|
|
}
|
|
|
|
|
|
|
|
rewrite:
|
|
|
|
done = write(fd, buf, *len);
|
|
|
|
if (done == -1 && errno == EINTR)
|
|
|
|
goto rewrite;
|
|
|
|
if (done == -1 && errno != EAGAIN) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to write to container pty"));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
if (done > 0) {
|
|
|
|
memmove(buf, buf + done, (*len - done));
|
|
|
|
*len -= done;
|
|
|
|
} else {
|
|
|
|
VIR_DEBUG("Write fd %d done %d errno %d", fd, (int)done, errno);
|
2012-01-12 17:03:03 +00:00
|
|
|
if (watch == console->hostWatch)
|
|
|
|
console->hostBlocking = true;
|
|
|
|
else
|
|
|
|
console->contBlocking = true;
|
2011-10-19 15:22:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (events & VIR_EVENT_HANDLE_HANGUP) {
|
|
|
|
if (watch == console->hostWatch) {
|
|
|
|
console->hostClosed = true;
|
|
|
|
} else {
|
|
|
|
console->contClosed = true;
|
|
|
|
}
|
|
|
|
VIR_DEBUG("Got EOF on %d %d", watch, fd);
|
|
|
|
}
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
virLXCControllerConsoleUpdateWatch(console);
|
2011-10-19 15:22:24 +00:00
|
|
|
virMutexUnlock(&lock);
|
|
|
|
return;
|
|
|
|
|
|
|
|
error:
|
|
|
|
virEventRemoveHandle(console->contWatch);
|
|
|
|
virEventRemoveHandle(console->hostWatch);
|
|
|
|
console->contWatch = console->hostWatch = -1;
|
|
|
|
quit = true;
|
|
|
|
virMutexUnlock(&lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-08-13 10:25:34 +00:00
|
|
|
/**
|
2009-11-05 12:35:13 +00:00
|
|
|
* lxcControllerMain
|
2011-10-19 15:22:24 +00:00
|
|
|
* @serverFd: server socket fd to accept client requests
|
|
|
|
* @clientFd: initial client which is the libvirtd daemon
|
2008-08-13 10:25:34 +00:00
|
|
|
*
|
2011-10-19 15:22:24 +00:00
|
|
|
* Processes I/O on consoles and the monitor
|
2008-08-13 10:25:34 +00:00
|
|
|
*
|
|
|
|
* Returns 0 on success or -1 in case of error
|
|
|
|
*/
|
2012-07-03 13:40:45 +00:00
|
|
|
static int virLXCControllerMain(virLXCControllerPtr ctrl)
|
2008-08-13 10:25:34 +00:00
|
|
|
{
|
2011-10-19 15:22:24 +00:00
|
|
|
virErrorPtr err;
|
2008-08-13 10:25:34 +00:00
|
|
|
int rc = -1;
|
2011-10-20 08:44:31 +00:00
|
|
|
size_t i;
|
2011-10-19 15:22:24 +00:00
|
|
|
|
|
|
|
if (virMutexInit(&lock) < 0)
|
|
|
|
goto cleanup2;
|
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
if (virNetServerAddSignalHandler(ctrl->server,
|
|
|
|
SIGCHLD,
|
|
|
|
virLXCControllerSignalChildIO,
|
|
|
|
ctrl) < 0)
|
2008-08-13 10:25:34 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2011-10-19 15:22:24 +00:00
|
|
|
virResetLastError();
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
for (i = 0 ; i < ctrl->nconsoles ; i++) {
|
|
|
|
if ((ctrl->consoles[i].epollFd = epoll_create1(EPOLL_CLOEXEC)) < 0) {
|
2012-01-12 17:03:03 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to create epoll fd"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
if ((ctrl->consoles[i].epollWatch = virEventAddHandle(ctrl->consoles[i].epollFd,
|
|
|
|
VIR_EVENT_HANDLE_READABLE,
|
|
|
|
virLXCControllerConsoleEPoll,
|
|
|
|
&(ctrl->consoles[i]),
|
|
|
|
NULL)) < 0) {
|
2012-01-12 17:03:03 +00:00
|
|
|
lxcError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Unable to watch epoll FD"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
if ((ctrl->consoles[i].hostWatch = virEventAddHandle(ctrl->consoles[i].hostFd,
|
|
|
|
VIR_EVENT_HANDLE_READABLE,
|
|
|
|
virLXCControllerConsoleIO,
|
|
|
|
&(ctrl->consoles[i]),
|
|
|
|
NULL)) < 0) {
|
2011-10-20 08:44:31 +00:00
|
|
|
lxcError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Unable to watch host console PTY"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
if ((ctrl->consoles[i].contWatch = virEventAddHandle(ctrl->consoles[i].contFd,
|
|
|
|
VIR_EVENT_HANDLE_READABLE,
|
|
|
|
virLXCControllerConsoleIO,
|
|
|
|
&(ctrl->consoles[i]),
|
|
|
|
NULL)) < 0) {
|
2011-10-20 08:44:31 +00:00
|
|
|
lxcError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Unable to watch host console PTY"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2011-10-19 15:22:24 +00:00
|
|
|
}
|
2008-08-13 10:25:34 +00:00
|
|
|
|
2011-10-19 15:22:24 +00:00
|
|
|
virMutexLock(&lock);
|
|
|
|
while (!quit) {
|
|
|
|
virMutexUnlock(&lock);
|
|
|
|
if (virEventRunDefaultImpl() < 0)
|
2008-08-13 10:25:34 +00:00
|
|
|
goto cleanup;
|
2011-10-19 15:22:24 +00:00
|
|
|
virMutexLock(&lock);
|
2008-08-13 10:25:34 +00:00
|
|
|
}
|
2011-10-19 15:22:24 +00:00
|
|
|
virMutexUnlock(&lock);
|
2008-08-13 10:25:34 +00:00
|
|
|
|
2011-10-19 15:22:24 +00:00
|
|
|
err = virGetLastError();
|
|
|
|
if (!err || err->code == VIR_ERR_OK)
|
|
|
|
rc = 0;
|
2008-08-13 10:25:34 +00:00
|
|
|
|
|
|
|
cleanup:
|
2011-10-19 15:22:24 +00:00
|
|
|
virMutexDestroy(&lock);
|
|
|
|
cleanup2:
|
2012-01-12 17:03:03 +00:00
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
for (i = 0 ; i < ctrl->nconsoles ; i++)
|
|
|
|
virLXCControllerConsoleClose(&(ctrl->consoles[i]));
|
2012-01-12 17:03:03 +00:00
|
|
|
|
2008-08-13 10:25:34 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2008-08-13 10:52:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-07-03 11:06:38 +00:00
|
|
|
* virLXCControllerMoveInterfaces
|
2008-08-13 10:52:15 +00:00
|
|
|
* @nveths: number of interfaces
|
|
|
|
* @veths: interface names
|
|
|
|
* @container: pid of container
|
|
|
|
*
|
|
|
|
* Moves network interfaces into a container's namespace
|
|
|
|
*
|
|
|
|
* Returns 0 on success or -1 in case of error
|
|
|
|
*/
|
2012-07-03 11:12:59 +00:00
|
|
|
static int virLXCControllerMoveInterfaces(virLXCControllerPtr ctrl)
|
2008-08-13 10:52:15 +00:00
|
|
|
{
|
2012-07-03 11:06:38 +00:00
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0 ; i < ctrl->nveths ; i++) {
|
2012-07-03 11:12:59 +00:00
|
|
|
if (virNetDevSetNamespace(ctrl->veths[i], ctrl->initpid) < 0)
|
2008-08-13 10:52:15 +00:00
|
|
|
return -1;
|
2012-07-03 11:06:38 +00:00
|
|
|
}
|
2008-08-13 10:52:15 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-07-03 11:06:38 +00:00
|
|
|
* virLXCControllerDeleteInterfaces:
|
|
|
|
* @ctrl: the LXC controller
|
2008-08-13 10:52:15 +00:00
|
|
|
*
|
|
|
|
* Cleans up the container interfaces by deleting the veth device pairs.
|
|
|
|
*
|
|
|
|
* Returns 0 on success or -1 in case of error
|
|
|
|
*/
|
2012-07-03 11:06:38 +00:00
|
|
|
static int virLXCControllerDeleteInterfaces(virLXCControllerPtr ctrl)
|
2008-08-13 10:52:15 +00:00
|
|
|
{
|
2012-07-03 11:06:38 +00:00
|
|
|
size_t i;
|
|
|
|
int ret = 0;
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2012-07-03 11:06:38 +00:00
|
|
|
for (i = 0 ; i < ctrl->nveths ; i++) {
|
|
|
|
if (virNetDevVethDelete(ctrl->veths[i]) < 0)
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2008-08-13 10:52:15 +00:00
|
|
|
}
|
|
|
|
|
2012-07-03 11:06:38 +00:00
|
|
|
|
2011-02-23 17:17:53 +00:00
|
|
|
static int lxcSetPersonality(virDomainDefPtr def)
|
|
|
|
{
|
|
|
|
struct utsname utsname;
|
|
|
|
const char *altArch;
|
|
|
|
|
|
|
|
uname(&utsname);
|
|
|
|
|
|
|
|
altArch = lxcContainerGetAlt32bitArch(utsname.machine);
|
|
|
|
if (altArch &&
|
|
|
|
STREQ(def->os.arch, altArch)) {
|
|
|
|
if (personality(PER_LINUX32) < 0) {
|
|
|
|
virReportSystemError(errno, _("Unable to request personality for %s on %s"),
|
|
|
|
altArch, utsname.machine);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-04-22 14:26:50 +00:00
|
|
|
#ifndef MS_REC
|
2010-03-09 18:22:22 +00:00
|
|
|
# define MS_REC 16384
|
2009-04-22 14:26:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef MS_SLAVE
|
2010-03-09 18:22:22 +00:00
|
|
|
# define MS_SLAVE (1<<19)
|
2009-04-22 14:26:50 +00:00
|
|
|
#endif
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2011-10-19 01:39:57 +00:00
|
|
|
/* Create a private tty using the private devpts at PTMX, returning
|
|
|
|
* the master in *TTYMASTER and the name of the slave, _from the
|
|
|
|
* perspective of the guest after remounting file systems_, in
|
|
|
|
* *TTYNAME. Heavily borrowed from glibc, but doesn't require that
|
|
|
|
* devpts == "/dev/pts" */
|
|
|
|
static int
|
|
|
|
lxcCreateTty(char *ptmx, int *ttymaster, char **ttyName)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
int ptyno;
|
|
|
|
int unlock = 0;
|
|
|
|
|
|
|
|
if ((*ttymaster = open(ptmx, O_RDWR|O_NOCTTY|O_NONBLOCK)) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (ioctl(*ttymaster, TIOCSPTLCK, &unlock) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (ioctl(*ttymaster, TIOCGPTN, &ptyno) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
/* If mount() succeeded at honoring newinstance, then the kernel
|
|
|
|
* was new enough to also honor the mode=0620,gid=5 options, which
|
|
|
|
* guarantee that the new pty already has correct permissions; so
|
|
|
|
* while glibc has to fstat(), fchmod(), and fchown() for older
|
|
|
|
* kernels, we can skip those steps. ptyno shouldn't currently be
|
|
|
|
* anything other than 0, but let's play it safe. */
|
|
|
|
if (virAsprintf(ttyName, "/dev/pts/%d", ptyno) < 0) {
|
|
|
|
virReportOOMError();
|
|
|
|
errno = ENOMEM;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (ret != 0) {
|
|
|
|
VIR_FORCE_CLOSE(*ttymaster);
|
|
|
|
VIR_FREE(*ttyName);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
|
2008-08-13 10:52:15 +00:00
|
|
|
static int
|
2012-07-03 13:02:33 +00:00
|
|
|
virLXCControllerSetupDevPTS(virLXCControllerPtr ctrl)
|
2008-08-13 10:52:15 +00:00
|
|
|
{
|
2012-07-03 13:02:33 +00:00
|
|
|
virDomainFSDefPtr root = virDomainGetRootFilesystem(ctrl->def);
|
2012-05-11 10:02:50 +00:00
|
|
|
char *mount_options = NULL;
|
2012-07-03 13:02:33 +00:00
|
|
|
char *opts;
|
|
|
|
char *devpts = NULL;
|
|
|
|
int ret = -1;
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
if (!root) {
|
|
|
|
if (ctrl->nconsoles != 1) {
|
|
|
|
lxcError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("Expected exactly one console, but got %zu"),
|
|
|
|
ctrl->nconsoles);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
2011-06-02 15:52:32 +00:00
|
|
|
}
|
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
VIR_DEBUG("Setting up private /dev/pts");
|
2010-03-04 11:15:42 +00:00
|
|
|
|
2009-04-22 14:26:50 +00:00
|
|
|
/*
|
|
|
|
* If doing a chroot style setup, we need to prepare
|
|
|
|
* a private /dev/pts for the child now, which they
|
|
|
|
* will later move into position.
|
|
|
|
*
|
|
|
|
* This is complex because 'virsh console' needs to
|
|
|
|
* use /dev/pts from the host OS, and the guest OS
|
|
|
|
* needs to use /dev/pts from the guest.
|
|
|
|
*
|
|
|
|
* This means that we (libvirt_lxc) need to see and
|
|
|
|
* use both /dev/pts instances. We're running in the
|
|
|
|
* host OS context though and don't want to expose
|
|
|
|
* the guest OS /dev/pts there.
|
|
|
|
*
|
|
|
|
* Thus we call unshare(CLONE_NS) so that we can see
|
|
|
|
* the guest's new /dev/pts, without it becoming
|
|
|
|
* visible to the host OS. We also put the root FS
|
|
|
|
* into slave mode, just in case it was currently
|
|
|
|
* marked as shared
|
|
|
|
*/
|
2012-07-03 13:02:33 +00:00
|
|
|
mount_options = virSecurityManagerGetMountOptions(ctrl->securityManager,
|
|
|
|
ctrl->def);
|
2011-06-02 18:25:25 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
if (!virFileExists(root->src)) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("root source %s does not exist"),
|
|
|
|
root->src);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2011-06-02 18:25:25 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
if (unshare(CLONE_NEWNS) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Cannot unshare mount namespace"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2009-04-22 14:26:50 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
if (mount("", "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Failed to switch root mount into slave mode"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2009-04-22 14:26:50 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
if (virAsprintf(&devpts, "%s/dev/pts", root->src) < 0 ||
|
|
|
|
virAsprintf(&ctrl->devptmx, "%s/dev/pts/ptmx", root->src) < 0) {
|
|
|
|
virReportOOMError();
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2009-04-22 14:26:50 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
if (virFileMakePath(devpts) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Failed to make path %s"),
|
|
|
|
devpts);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2009-04-22 14:26:50 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
/* XXX should we support gid=X for X!=5 for distros which use
|
|
|
|
* a different gid for tty? */
|
|
|
|
if (virAsprintf(&opts, "newinstance,ptmxmode=0666,mode=0620,gid=5%s",
|
|
|
|
(mount_options ? mount_options : "")) < 0) {
|
|
|
|
virReportOOMError();
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2012-01-25 14:12:54 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
VIR_DEBUG("Mount devpts on %s type=tmpfs flags=%x, opts=%s",
|
|
|
|
devpts, MS_NOSUID, opts);
|
|
|
|
if (mount("devpts", devpts, "devpts", MS_NOSUID, opts) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Failed to mount devpts on %s"),
|
|
|
|
devpts);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2009-04-22 14:26:50 +00:00
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
if (access(ctrl->devptmx, R_OK) < 0) {
|
|
|
|
VIR_WARN("Kernel does not support private devpts, using shared devpts");
|
|
|
|
VIR_FREE(ctrl->devptmx);
|
2009-04-22 14:26:50 +00:00
|
|
|
}
|
|
|
|
|
2012-07-03 13:02:33 +00:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
VIR_FREE(opts);
|
|
|
|
VIR_FREE(devpts);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virLXCControllerSetupConsoles(virLXCControllerPtr ctrl,
|
|
|
|
char **containerTTYPaths)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
for (i = 0 ; i < ctrl->nconsoles ; i++) {
|
2012-07-03 13:02:33 +00:00
|
|
|
if (ctrl->devptmx) {
|
|
|
|
VIR_DEBUG("Opening tty on private %s", ctrl->devptmx);
|
|
|
|
if (lxcCreateTty(ctrl->devptmx,
|
2012-07-03 11:42:53 +00:00
|
|
|
&ctrl->consoles[i].contFd,
|
|
|
|
&containerTTYPaths[i]) < 0) {
|
2011-10-20 08:44:31 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Failed to allocate tty"));
|
2012-07-03 13:02:33 +00:00
|
|
|
return -1;
|
2011-10-20 08:44:31 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
VIR_DEBUG("Opening tty on shared /dev/ptmx");
|
2012-07-03 11:42:53 +00:00
|
|
|
if (virFileOpenTty(&ctrl->consoles[i].contFd,
|
|
|
|
&containerTTYPaths[i],
|
2011-10-20 08:44:31 +00:00
|
|
|
0) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Failed to allocate tty"));
|
2012-07-03 13:02:33 +00:00
|
|
|
return -1;
|
2011-10-20 08:44:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-07-03 13:02:33 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2012-07-03 13:40:45 +00:00
|
|
|
virLXCControllerRun(virLXCControllerPtr ctrl)
|
2012-07-03 13:02:33 +00:00
|
|
|
{
|
|
|
|
int rc = -1;
|
|
|
|
int control[2] = { -1, -1};
|
|
|
|
int containerhandshake[2] = { -1, -1 };
|
|
|
|
char **containerTTYPaths = NULL;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (VIR_ALLOC_N(containerTTYPaths, ctrl->nconsoles) < 0) {
|
|
|
|
virReportOOMError();
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (socketpair(PF_UNIX, SOCK_STREAM, 0, control) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("sockpair failed"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (socketpair(PF_UNIX, SOCK_STREAM, 0, containerhandshake) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("socketpair failed"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virLXCControllerSetupLoopDevices(ctrl) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2012-07-03 13:53:59 +00:00
|
|
|
if (virLXCControllerSetupResourceLimits(ctrl) < 0)
|
2012-07-03 13:02:33 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (virLXCControllerSetupDevPTS(ctrl) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (virLXCControllerSetupConsoles(ctrl, containerTTYPaths) < 0)
|
|
|
|
goto cleanup;
|
2011-10-20 08:44:31 +00:00
|
|
|
|
2012-07-03 10:54:09 +00:00
|
|
|
if (lxcSetPersonality(ctrl->def) < 0)
|
2011-02-23 17:17:53 +00:00
|
|
|
goto cleanup;
|
2009-04-22 14:26:50 +00:00
|
|
|
|
2012-07-03 11:12:59 +00:00
|
|
|
if ((ctrl->initpid = lxcContainerStart(ctrl->def,
|
2012-07-03 12:59:36 +00:00
|
|
|
ctrl->securityManager,
|
2012-07-03 11:12:59 +00:00
|
|
|
ctrl->nveths,
|
|
|
|
ctrl->veths,
|
|
|
|
control[1],
|
|
|
|
containerhandshake[1],
|
2012-07-03 11:42:53 +00:00
|
|
|
containerTTYPaths,
|
|
|
|
ctrl->nconsoles)) < 0)
|
2008-08-13 10:52:15 +00:00
|
|
|
goto cleanup;
|
2010-11-09 20:48:48 +00:00
|
|
|
VIR_FORCE_CLOSE(control[1]);
|
2011-06-02 15:52:32 +00:00
|
|
|
VIR_FORCE_CLOSE(containerhandshake[1]);
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2012-07-03 11:12:59 +00:00
|
|
|
if (virLXCControllerMoveInterfaces(ctrl) < 0)
|
2008-08-13 10:52:15 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2011-06-02 15:18:14 +00:00
|
|
|
if (lxcContainerSendContinue(control[0]) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to send container continue message"));
|
2008-08-13 10:52:15 +00:00
|
|
|
goto cleanup;
|
2011-06-02 15:18:14 +00:00
|
|
|
}
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2011-06-02 15:52:32 +00:00
|
|
|
if (lxcContainerWaitForContinue(containerhandshake[0]) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("error receiving signal from container"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2011-08-04 09:13:02 +00:00
|
|
|
/* Now the container is fully setup... */
|
|
|
|
|
|
|
|
/* ...we can close the loop devices... */
|
2012-07-03 12:49:19 +00:00
|
|
|
if (virLXCControllerCloseLoopDevices(ctrl, false) < 0)
|
|
|
|
goto cleanup;
|
2011-08-04 09:13:02 +00:00
|
|
|
|
|
|
|
/* ...and reduce our privileges */
|
2009-06-29 17:09:42 +00:00
|
|
|
if (lxcControllerClearCapabilities() < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2012-07-03 12:16:48 +00:00
|
|
|
if (virLXCControllerDaemonHandshake(ctrl) < 0)
|
2011-06-01 22:17:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
for (i = 0 ; i < ctrl->nconsoles ; i++)
|
|
|
|
if (virLXCControllerConsoleSetNonblocking(&(ctrl->consoles[i])) < 0)
|
2011-10-20 08:44:31 +00:00
|
|
|
goto cleanup;
|
2011-10-19 15:22:24 +00:00
|
|
|
|
2012-07-03 13:40:45 +00:00
|
|
|
rc = virLXCControllerMain(ctrl);
|
2008-08-13 10:52:15 +00:00
|
|
|
|
|
|
|
cleanup:
|
2010-11-09 20:48:48 +00:00
|
|
|
VIR_FORCE_CLOSE(control[0]);
|
|
|
|
VIR_FORCE_CLOSE(control[1]);
|
2011-06-02 15:52:32 +00:00
|
|
|
VIR_FORCE_CLOSE(containerhandshake[0]);
|
|
|
|
VIR_FORCE_CLOSE(containerhandshake[1]);
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
for (i = 0 ; i < ctrl->nconsoles ; i++)
|
|
|
|
VIR_FREE(containerTTYPaths[i]);
|
|
|
|
VIR_FREE(containerTTYPaths);
|
2011-10-27 07:18:00 +00:00
|
|
|
|
2012-07-03 11:12:59 +00:00
|
|
|
virLXCControllerStopInit(ctrl);
|
2011-10-21 17:09:23 +00:00
|
|
|
|
2008-08-13 10:52:15 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
int main(int argc, char *argv[])
|
2008-08-13 10:52:15 +00:00
|
|
|
{
|
|
|
|
pid_t pid;
|
2008-08-20 20:55:32 +00:00
|
|
|
int rc = 1;
|
|
|
|
char *name = NULL;
|
2012-07-03 11:06:38 +00:00
|
|
|
size_t nveths = 0;
|
2008-08-20 20:55:32 +00:00
|
|
|
char **veths = NULL;
|
2012-07-03 12:16:48 +00:00
|
|
|
int handshakeFd = -1;
|
2008-08-20 20:55:32 +00:00
|
|
|
int bg = 0;
|
2009-02-03 13:08:59 +00:00
|
|
|
const struct option options[] = {
|
2008-08-20 20:55:32 +00:00
|
|
|
{ "background", 0, NULL, 'b' },
|
|
|
|
{ "name", 1, NULL, 'n' },
|
|
|
|
{ "veth", 1, NULL, 'v' },
|
|
|
|
{ "console", 1, NULL, 'c' },
|
2011-06-01 22:17:00 +00:00
|
|
|
{ "handshakefd", 1, NULL, 's' },
|
2012-01-25 14:12:53 +00:00
|
|
|
{ "security", 1, NULL, 'S' },
|
2008-08-20 20:55:32 +00:00
|
|
|
{ "help", 0, NULL, 'h' },
|
|
|
|
{ 0, 0, 0, 0 },
|
|
|
|
};
|
2011-10-20 08:44:31 +00:00
|
|
|
int *ttyFDs = NULL;
|
|
|
|
size_t nttyFDs = 0;
|
2012-07-03 10:54:09 +00:00
|
|
|
virLXCControllerPtr ctrl = NULL;
|
2012-07-03 11:42:53 +00:00
|
|
|
size_t i;
|
2012-07-03 12:59:36 +00:00
|
|
|
const char *securityDriver = "none";
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2010-11-16 19:01:37 +00:00
|
|
|
if (setlocale(LC_ALL, "") == NULL ||
|
|
|
|
bindtextdomain(PACKAGE, LOCALEDIR) == NULL ||
|
Initialize random generator in lxc controller
The lxc contoller eventually makes use of virRandomBits(), which was
segfaulting since virRandomInitialize() is never invoked.
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff554d560 in random_r () from /lib64/libc.so.6
(gdb) bt
0 0x00007ffff554d560 in random_r () from /lib64/libc.so.6
1 0x0000000000469eaa in virRandomBits (nbits=32) at util/virrandom.c:80
2 0x000000000045bf69 in virHashCreateFull (size=256,
dataFree=0x4aa2a2 <hashDataFree>, keyCode=0x45bd40 <virHashStrCode>,
keyEqual=0x45bdad <virHashStrEqual>, keyCopy=0x45bdfa <virHashStrCopy>,
keyFree=0x45be37 <virHashStrFree>) at util/virhash.c:134
3 0x000000000045c069 in virHashCreate (size=0, dataFree=0x4aa2a2 <hashDataFree>)
at util/virhash.c:164
4 0x00000000004aa562 in virNWFilterHashTableCreate (n=0)
at conf/nwfilter_params.c:686
5 0x00000000004aa95b in virNWFilterParseParamAttributes (cur=0x711d30)
at conf/nwfilter_params.c:793
6 0x0000000000481a7f in virDomainNetDefParseXML (caps=0x702c90, node=0x7116b0,
ctxt=0x7101b0, bootMap=0x0, flags=0) at conf/domain_conf.c:4589
7 0x000000000048cc36 in virDomainDefParseXML (caps=0x702c90, xml=0x710040,
root=0x7103b0, ctxt=0x7101b0, expectedVirtTypes=16, flags=0)
at conf/domain_conf.c:8658
8 0x000000000048f011 in virDomainDefParseNode (caps=0x702c90, xml=0x710040,
root=0x7103b0, expectedVirtTypes=16, flags=0) at conf/domain_conf.c:9360
9 0x000000000048ee30 in virDomainDefParse (xmlStr=0x0,
filename=0x702ae0 "/var/run/libvirt/lxc/x.xml", caps=0x702c90,
expectedVirtTypes=16, flags=0) at conf/domain_conf.c:9310
10 0x000000000048ef00 in virDomainDefParseFile (caps=0x702c90,
filename=0x702ae0 "/var/run/libvirt/lxc/x.xml", expectedVirtTypes=16, flags=0)
at conf/domain_conf.c:9332
11 0x0000000000425053 in main (argc=5, argv=0x7fffffffe2b8)
at lxc/lxc_controller.c:1773
2012-06-21 05:28:09 +00:00
|
|
|
textdomain(PACKAGE) == NULL ||
|
|
|
|
virRandomInitialize(time(NULL) ^ getpid())) {
|
2010-11-16 19:01:37 +00:00
|
|
|
fprintf(stderr, _("%s: initialization failed\n"), argv[0]);
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2012-05-01 09:47:53 +00:00
|
|
|
/* Initialize logging */
|
|
|
|
virLogSetFromEnv();
|
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
while (1) {
|
|
|
|
int c;
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2012-01-25 14:12:53 +00:00
|
|
|
c = getopt_long(argc, argv, "dn:v:m:c:s:h:S:",
|
2008-08-20 20:55:32 +00:00
|
|
|
options, NULL);
|
|
|
|
|
|
|
|
if (c == -1)
|
|
|
|
break;
|
|
|
|
|
|
|
|
switch (c) {
|
|
|
|
case 'b':
|
|
|
|
bg = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'n':
|
|
|
|
if ((name = strdup(optarg)) == NULL) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2008-08-20 20:55:32 +00:00
|
|
|
goto cleanup;
|
2008-08-13 10:52:15 +00:00
|
|
|
}
|
2008-08-20 20:55:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'v':
|
|
|
|
if (VIR_REALLOC_N(veths, nveths+1) < 0) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2008-08-20 20:55:32 +00:00
|
|
|
goto cleanup;
|
2008-08-13 10:52:15 +00:00
|
|
|
}
|
2008-08-20 20:55:32 +00:00
|
|
|
if ((veths[nveths++] = strdup(optarg)) == NULL) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2008-08-20 20:55:32 +00:00
|
|
|
goto cleanup;
|
2008-08-13 10:52:15 +00:00
|
|
|
}
|
2008-08-20 20:55:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'c':
|
2011-10-20 08:44:31 +00:00
|
|
|
if (VIR_REALLOC_N(ttyFDs, nttyFDs + 1) < 0) {
|
|
|
|
virReportOOMError();
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (virStrToLong_i(optarg, NULL, 10, &ttyFDs[nttyFDs++]) < 0) {
|
2008-08-20 20:55:32 +00:00
|
|
|
fprintf(stderr, "malformed --console argument '%s'", optarg);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2011-06-01 22:17:00 +00:00
|
|
|
case 's':
|
2012-07-03 12:16:48 +00:00
|
|
|
if (virStrToLong_i(optarg, NULL, 10, &handshakeFd) < 0) {
|
2011-06-01 22:17:00 +00:00
|
|
|
fprintf(stderr, "malformed --handshakefd argument '%s'",
|
|
|
|
optarg);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2012-01-25 14:12:53 +00:00
|
|
|
case 'S':
|
2012-07-03 12:59:36 +00:00
|
|
|
securityDriver = optarg;
|
2012-01-25 14:12:53 +00:00
|
|
|
break;
|
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
case 'h':
|
|
|
|
case '?':
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
fprintf(stderr, "syntax: %s [OPTIONS]\n", argv[0]);
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
fprintf(stderr, "Options\n");
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
fprintf(stderr, " -b, --background\n");
|
|
|
|
fprintf(stderr, " -n NAME, --name NAME\n");
|
|
|
|
fprintf(stderr, " -c FD, --console FD\n");
|
|
|
|
fprintf(stderr, " -v VETH, --veth VETH\n");
|
2011-06-01 22:17:00 +00:00
|
|
|
fprintf(stderr, " -s FD, --handshakefd FD\n");
|
2012-01-25 14:12:53 +00:00
|
|
|
fprintf(stderr, " -S NAME, --security NAME\n");
|
2008-08-20 20:55:32 +00:00
|
|
|
fprintf(stderr, " -h, --help\n");
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
goto cleanup;
|
2008-08-13 10:52:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
if (name == NULL) {
|
|
|
|
fprintf(stderr, "%s: missing --name argument for configuration\n", argv[0]);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2012-07-03 12:16:48 +00:00
|
|
|
if (handshakeFd < 0) {
|
2011-06-01 22:17:00 +00:00
|
|
|
fprintf(stderr, "%s: missing --handshake argument for container PTY\n",
|
|
|
|
argv[0]);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2009-11-10 11:56:11 +00:00
|
|
|
if (getuid() != 0) {
|
2008-08-20 20:55:32 +00:00
|
|
|
fprintf(stderr, "%s: must be run as the 'root' user\n", argv[0]);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2011-10-19 15:22:24 +00:00
|
|
|
virEventRegisterDefaultImpl();
|
|
|
|
|
2012-07-03 10:54:09 +00:00
|
|
|
if (!(ctrl = virLXCControllerNew(name)))
|
2008-08-20 20:55:32 +00:00
|
|
|
goto cleanup;
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2012-07-03 12:16:48 +00:00
|
|
|
ctrl->handshakeFd = handshakeFd;
|
|
|
|
|
2012-07-03 12:59:36 +00:00
|
|
|
if (!(ctrl->securityManager = virSecurityManagerNew(securityDriver,
|
|
|
|
LXC_DRIVER_NAME,
|
|
|
|
false, false, false)))
|
|
|
|
goto cleanup;
|
|
|
|
|
2012-05-01 09:47:53 +00:00
|
|
|
VIR_DEBUG("Security model %s type %s label %s imagelabel %s",
|
2012-07-03 10:54:09 +00:00
|
|
|
NULLSTR(ctrl->def->seclabel.model),
|
|
|
|
virDomainSeclabelTypeToString(ctrl->def->seclabel.type),
|
|
|
|
NULLSTR(ctrl->def->seclabel.label),
|
|
|
|
NULLSTR(ctrl->def->seclabel.imagelabel));
|
2012-05-01 09:47:53 +00:00
|
|
|
|
2012-07-03 11:06:38 +00:00
|
|
|
ctrl->veths = veths;
|
|
|
|
ctrl->nveths = nveths;
|
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
for (i = 0 ; i < nttyFDs ; i++) {
|
|
|
|
if (virLXCControllerAddConsole(ctrl, ttyFDs[i]) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
ttyFDs[i] = -1;
|
|
|
|
}
|
|
|
|
|
2012-07-03 11:06:38 +00:00
|
|
|
if (virLXCControllerValidateNICs(ctrl) < 0)
|
2008-08-20 20:55:32 +00:00
|
|
|
goto cleanup;
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2012-07-03 11:42:53 +00:00
|
|
|
if (virLXCControllerValidateConsoles(ctrl) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2012-07-03 14:25:30 +00:00
|
|
|
if (virLXCControllerSetupServer(ctrl) < 0)
|
2008-08-20 20:55:32 +00:00
|
|
|
goto cleanup;
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
if (bg) {
|
|
|
|
if ((pid = fork()) < 0)
|
|
|
|
goto cleanup;
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
if (pid > 0) {
|
2011-08-05 13:13:12 +00:00
|
|
|
if ((rc = virPidFileWrite(LXC_STATE_DIR, name, pid)) < 0) {
|
2011-07-25 18:11:38 +00:00
|
|
|
virReportSystemError(-rc,
|
2009-01-20 17:13:33 +00:00
|
|
|
_("Unable to write pid file '%s/%s.pid'"),
|
|
|
|
LXC_STATE_DIR, name);
|
2008-08-20 20:55:32 +00:00
|
|
|
_exit(1);
|
|
|
|
}
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
/* First child now exits, allowing original caller
|
|
|
|
* (ie libvirtd's LXC driver to complete their
|
|
|
|
* waitpid & continue */
|
|
|
|
_exit(0);
|
2008-08-13 10:52:15 +00:00
|
|
|
}
|
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
/* Don't hold onto any cwd we inherit from libvirtd either */
|
|
|
|
if (chdir("/") < 0) {
|
2010-02-04 20:02:58 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
2009-01-20 17:13:33 +00:00
|
|
|
_("Unable to change to root dir"));
|
2008-08-20 20:55:32 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (setsid() < 0) {
|
2010-02-04 20:02:58 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
2009-01-20 17:13:33 +00:00
|
|
|
_("Unable to become session leader"));
|
2008-08-20 20:55:32 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2012-07-03 13:40:45 +00:00
|
|
|
rc = virLXCControllerRun(ctrl);
|
2008-08-13 10:52:15 +00:00
|
|
|
|
2008-08-20 20:55:32 +00:00
|
|
|
cleanup:
|
2012-07-03 10:54:09 +00:00
|
|
|
virPidFileDelete(LXC_STATE_DIR, name);
|
2012-07-03 11:06:38 +00:00
|
|
|
virLXCControllerDeleteInterfaces(ctrl);
|
2012-07-03 11:42:53 +00:00
|
|
|
for (i = 0 ; i < nttyFDs ; i++)
|
|
|
|
VIR_FORCE_CLOSE(ttyFDs[i]);
|
|
|
|
VIR_FREE(ttyFDs);
|
|
|
|
|
2012-07-03 10:54:09 +00:00
|
|
|
virLXCControllerFree(ctrl);
|
2008-08-20 20:55:32 +00:00
|
|
|
|
2011-07-25 18:11:38 +00:00
|
|
|
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
2008-08-20 20:55:32 +00:00
|
|
|
}
|