26 #include <sys/types.h> 27 #include <sys/mount.h> 31 #include <sys/resource.h> 33 #include <lxc/lxccontainer.h> 34 #include <lxc/version.h> 45 #include "container.h" 46 #include "filecleanuphandler.h" 50 #include "softwarecontainererror.h" 54 static constexpr
const char *LXC_CONTAINERS_ROOT_CONFIG_ITEM =
"lxc.lxcpath";
56 std::vector<const char *> Container::s_LXCContainerStates;
57 const char *Container::s_LXCRoot;
59 void Container::init_lxc()
61 static bool bInitialized =
false;
63 int stateCount = lxc_get_wait_states(
nullptr);
64 s_LXCContainerStates.resize(stateCount);
65 lxc_get_wait_states(s_LXCContainerStates.data());
66 if ((
int)LXCContainerState::ELEMENT_COUNT != s_LXCContainerStates.size()) {
67 log_error() <<
"Internal SC/LXC state mis-match, fatal error";
70 s_LXCRoot = lxc_get_global_config_item(LXC_CONTAINERS_ROOT_CONFIG_ITEM);
76 const std::string &configFile,
77 const std::string &containerRoot,
78 bool writeBufferEnabled,
79 int shutdownTimeout) :
80 m_configFile(configFile),
82 m_containerRoot(containerRoot),
83 m_writeBufferEnabled(writeBufferEnabled),
84 m_shutdownTimeout(shutdownTimeout)
87 log_debug() <<
"Container constructed with " << id;
90 Container::~Container()
92 if (m_container !=
nullptr) {
95 if (m_state >= ContainerState::STARTED) {
99 if (m_state >= ContainerState::CREATED) {
104 lxc_container_put(m_container);
105 m_container =
nullptr;
111 if (m_state < ContainerState::PREPARED) {
112 std::string gatewayDir = gatewaysDir();
113 std::unique_ptr<CreateDir> createDirInstance = std::unique_ptr<CreateDir>(
new CreateDir());
114 if (!createDirInstance->createDirectory(gatewayDir)) {
115 log_error() <<
"Could not create gateway directory " 116 << gatewayDir <<
": " << strerror(errno);
121 log_error() <<
"Could not create shared mount point for dir: " << gatewayDir;
125 m_state = ContainerState::PREPARED;
130 std::string Container::toString()
132 std::stringstream ss;
133 ss <<
"LXC " << id() <<
" ";
134 if (m_container !=
nullptr) {
136 <<
" / state:" << m_container->state(m_container)
137 <<
" / initPID:" << m_container->init_pid(m_container)
138 <<
" / LastError: " << m_container->error_string
139 <<
" / To connect to this container : lxc-attach -n " << id();
147 if (m_state >= ContainerState::CREATED) {
148 log_warning() <<
"Container already created";
152 log_debug() <<
"Creating container " << toString();
154 const char *containerID = id();
155 if (strlen(containerID) == 0) {
156 log_error() <<
"ContainerID cannot be empty";
160 setenv(
"GATEWAY_DIR", gatewaysDir().c_str(),
true);
161 log_debug() <<
"GATEWAY_DIR : " << Glib::getenv(
"GATEWAY_DIR");
163 auto configFile = m_configFile.c_str();
164 log_debug() <<
"Config file : " << configFile;
165 log_debug() <<
"Template : " << LXCTEMPLATE;
166 log_debug() <<
"creating container with ID : " << containerID;
170 m_container = lxc_container_new(containerID,
nullptr);
172 log_error() <<
"Error creating a new container";
173 return rollbackCreate();
176 if (m_container->is_defined(m_container)) {
177 log_error() <<
"ContainerID '" << containerID <<
"' is already in use.";
178 return rollbackCreate();
180 log_debug() <<
"Successfully created container struct";
182 if (!m_container->load_config(m_container, configFile)) {
183 log_error() <<
"Error loading container config";
184 return rollbackCreate();
187 log_debug() <<
"Successfully loaded container config";
190 m_rootFSPath = buildPath(s_LXCRoot, containerID,
"rootfs");
192 if (m_writeBufferEnabled) {
193 const std::string rootFSPathLower = m_containerRoot +
"/rootfs-lower";
194 const std::string rootFSPathUpper = m_containerRoot +
"/rootfs-upper";
195 const std::string rootFSPathWork = m_containerRoot +
"/rootfs-work";
197 overlayMount(rootFSPathLower, rootFSPathUpper, rootFSPathWork, m_rootFSPath);
198 log_debug() <<
"Write buffer enabled, lowerdir=" << rootFSPathLower
199 <<
", upperdir=" << rootFSPathUpper
200 <<
", workdir=" << rootFSPathWork
201 <<
", dst=" << m_rootFSPath;
203 log_debug() <<
"WriteBuffer disabled, dst=" << m_rootFSPath;
208 std::vector<char *> argv;
209 if (!m_container->create(m_container, LXCTEMPLATE,
nullptr,
nullptr, flags, &argv[0])) {
210 log_error() <<
"Error creating container";
211 m_rootFSPath.assign(
"");
212 return rollbackCreate();
216 m_state = ContainerState::CREATED;
217 log_debug() <<
"Container created. RootFS: " << m_rootFSPath;
222 bool Container::rollbackCreate() {
224 lxc_container_put(m_container);
225 m_container =
nullptr;
230 bool Container::ensureContainerRunning()
232 if (ContainerState::FROZEN == m_state) {
233 log_error() <<
"Container is frozen, does not run";
237 if (m_state < ContainerState::STARTED) {
238 log_error() <<
"Container is not in state STARTED, state is " << ((int)m_state);
239 log_error() << logging::getStackTrace();
243 if (!m_container->is_running(m_container)) {
244 return waitForState(LXCContainerState::RUNNING);
250 bool Container::waitForState(LXCContainerState state,
int timeout)
252 const char* currentState = m_container->state(m_container);
253 if (strcmp(currentState, toString(state))) {
254 log_debug() <<
"Waiting for container to change from " << currentState
255 <<
" to state : " << toString(state);
256 bool expired = m_container->wait(m_container, toString(state), timeout);
258 log_error() <<
"Container did not reach" << toString(state) <<
" in time";
267 if (m_state < ContainerState::CREATED) {
268 log_warning() <<
"Trying to start container that isn't created. Please create the container first";
272 if (pid ==
nullptr) {
273 log_error() <<
"Supplied pid argument is nullptr";
278 log_debug() <<
"Starting container";
280 char commandEnv[] =
"env";
281 char commandSleep[] =
"/bin/sleep";
282 char commandSleepTime[] =
"100000000";
283 char*
const args[] = { commandEnv, commandSleep, commandSleepTime,
nullptr };
285 if (!m_container->start(m_container,
false, args)) {
286 log_error() <<
"Error starting container";
290 log_debug() <<
"Container started: " << toString();
291 *pid = m_container->init_pid(m_container);
292 m_state = ContainerState::STARTED;
294 if (!ensureContainerRunning()) {
295 log_error() <<
"Container started but is not running";
299 log_info() <<
"To connect to this container : lxc-attach -n " << id();
303 int Container::unlimitCoreDump()
306 if (getrlimit(RLIMIT_CORE, &rlim) != 0) {
311 rlim.rlim_cur = rlim.rlim_max;
312 if (setrlimit(RLIMIT_CORE, &rlim) != 0) {
319 bool Container::setCgroupItem(std::string subsys, std::string value)
321 return m_container->set_cgroup_item(m_container, subsys.c_str(), value.c_str());
324 int Container::executeInContainerEntryFunction(
void *param)
326 int canCoreDump = unlimitCoreDump();
327 if (canCoreDump != 0) {
331 ExecFunction *
function = (ExecFunction *) param;
332 return (*
function)();
337 const EnvironmentVariables &variables,
342 bool execResult =
execute(
function, pid, variables, stdin_var, stdout_var, stderr_var);
346 return execResult && (0 == status);
351 const EnvironmentVariables &variables,
356 if (pid ==
nullptr) {
357 log_error() <<
"Supplied pid argument is nullptr";
361 if (!ensureContainerRunning()) {
362 log_error() <<
"Container is not running or in bad state, can't execute";
366 lxc_attach_options_t options = LXC_ATTACH_OPTIONS_DEFAULT;
367 options.stdin_fd = stdin;
368 options.stdout_fd = stdout;
369 options.stderr_fd = stderr;
371 options.uid = ROOT_UID;
372 options.gid = ROOT_UID;
375 EnvironmentVariables actualVariables = variables;
379 for (
auto &var : m_gatewayEnvironmentVariables) {
380 if (variables.count(var.first) != 0) {
381 if (m_gatewayEnvironmentVariables.at(var.first) != variables.at(var.first)) {
383 log_info() <<
"Variable \"" << var.first
384 <<
"\" set by gateway will be overwritten with the value: \"" 385 << variables.at(var.first) <<
"\"";
387 actualVariables[var.first] = variables.at(var.first);
390 actualVariables[var.first] = var.second;
394 log_debug() <<
"Starting function in container " << toString();
397 std::vector<std::string> strings;
398 for (
auto &var : actualVariables) {
399 strings.push_back(var.first +
"=" + var.second);
402 const size_t stringCount = strings.size() + 1;
403 const char **envVariablesArray =
new const char* [stringCount];
404 for (
size_t i = 0; i < strings.size(); i++) {
405 envVariablesArray[i] = strings[i].c_str();
406 log_debug() <<
"Passing env variable: " << strings[i];
409 envVariablesArray[strings.size()] =
nullptr;
410 options.extra_env_vars = (
char **)envVariablesArray;
413 int attach_res = m_container->attach(m_container,
414 &Container::executeInContainerEntryFunction,
419 delete[] envVariablesArray;
420 if (attach_res == 0) {
421 log_info() <<
" Attached PID: " << *pid;
424 log_error() <<
"Attach call to LXC container failed: " << std::string(strerror(errno));
430 const EnvironmentVariables &variables,
431 const std::string &workingDirectory,
432 int stdin,
int stdout,
int stderr)
434 if (!ensureContainerRunning()) {
435 log_error() <<
"Container is not running or in bad state, can't attach";
439 if (pid ==
nullptr) {
440 log_error() <<
"Supplied pid argument is nullptr";
444 log_debug() <<
"Attach " << commandLine ;
446 std::vector<std::string> executeCommandVec = Glib::shell_parse_argv(commandLine);
447 std::vector<char *> args;
449 for (
size_t i = 0; i < executeCommandVec.size(); i++) {
450 executeCommandVec[i].c_str();
451 auto s = &executeCommandVec[i][0];
456 args.push_back(
nullptr);
459 if (workingDirectory.length() != 0) {
460 auto ret = chdir(workingDirectory.c_str());
462 log_error() <<
"Error when changing current directory : " 466 execvp(args[0], args.data());
468 }, pid, variables, stdin, stdout, stderr);
470 log_error() <<
"Could not execute in container";
480 if (m_state >= ContainerState::STARTED) {
481 log_debug() <<
"Stopping the container";
482 if (m_container->stop(m_container)) {
483 log_debug() <<
"Container stopped, waiting for stop state";
484 waitForState(LXCContainerState::STOPPED);
486 log_error() <<
"Unable to stop container";
490 log_error() <<
"Can't stop container that has not been started";
504 if (m_state < ContainerState::STARTED) {
505 log_error() <<
"Trying to shutdown container that has not been started. Aborting";
509 log_debug() <<
"Shutting down container " << toString() <<
" pid: " 510 << m_container->init_pid(m_container);
512 if (m_container->init_pid(m_container) != INVALID_PID) {
513 kill(m_container->init_pid(m_container), SIGTERM);
517 bool success = m_container->shutdown(m_container, timeout);
519 log_warning() <<
"Failed to cleanly shutdown container, forcing stop" << toString();
521 log_error() <<
"Failed to force stop the container" << toString();
526 m_state = ContainerState::CREATED;
532 return destroy(m_shutdownTimeout);
537 if (m_state < ContainerState::CREATED) {
538 log_error() <<
"Trying to destroy container that has not been created. Aborting destroy";
542 if (m_state >= ContainerState::STARTED) {
544 log_error() <<
"Could not shutdown container. Aborting destroy";
551 if (m_writeBufferEnabled)
553 log_debug() <<
"Unmounting the overlay rootfs";
554 if(-1 == umount(m_rootFSPath.c_str())) {
555 log_error() <<
"Unmounting the overlay rootfs failed: " << strerror(errno);
561 bool success = m_container->destroy(m_container);
563 log_error() <<
"Failed to destroy the container " << toString();
567 m_state = ContainerState::DESTROYED;
572 const std::string &pathInContainer,
576 log_error() <<
"Path on host does not exist: " << pathInHost;
581 pid_t pid = INVALID_PID;
582 bool checkIfAlreadyMountpoint =
executeSync([pathInContainer] () {
583 FILE *mountsfile = setmntent(
"/proc/mounts",
"r");
584 struct mntent *mounts;
585 while ((mounts = getmntent(mountsfile)) !=
nullptr) {
586 std::string mountDir(mounts->mnt_dir);
587 if (0 == mountDir.compare(pathInContainer)) {
594 if (!checkIfAlreadyMountpoint) {
595 log_error() << pathInContainer <<
" is already mounted to.";
600 std::string filePart = baseName(pathInContainer);
601 std::string tempPath = buildPath(gatewaysDir(), filePart);
602 bool pathIsDirectory =
false;
603 std::unique_ptr<CreateDir> createDirInstance = std::unique_ptr<CreateDir>(
new CreateDir());
610 pathIsDirectory =
true;
611 log_debug() <<
"Path on host (" << pathInHost <<
") is directory, mounting as a directory";
613 log_debug() <<
"Creating folder : " << tempPath;
614 if (!createDirInstance->createDirectory(tempPath)) {
615 log_error() <<
"Could not create folder " << tempPath;
620 log_debug() <<
"Path on host (" << pathInHost <<
") is not a directory, " 621 <<
"mounting assuming it behaves like a file";
624 if (!touch(tempPath)) {
625 log_error() <<
"Could not create file " << tempPath;
631 if (!bindMountCore(pathInHost, pathInContainer, tempPath, readOnly)) {
640 if (!pathIsDirectory) {
648 bool Container::bindMountCore(
const std::string &pathInHost,
649 const std::string &pathInContainer,
650 const std::string &tempDirInContainerOnHost,
653 if (!ensureContainerRunning()) {
654 log_error() <<
"Container is not running or in bad state, can't bind-mount folder";
658 if (pathInContainer.front() !=
'/') {
659 log_error() <<
"Provided path '" << pathInContainer <<
"' is not absolute!";
665 tempDirInContainerOnHost,
668 m_writeBufferEnabled)) {
669 log_error() <<
"Could not bind mount " << pathInHost <<
" to " << tempDirInContainerOnHost;
674 std::string filePart = baseName(pathInContainer);
675 std::string tempDirInContainer = buildPath(gatewaysDirInContainer(), filePart);
677 pid_t pid = INVALID_PID;
680 auto createParentDirectories = [
this] (std::string path) {
681 std::stack<std::string> paths;
682 std::string currentPath = path;
684 paths.push(currentPath);
685 currentPath = parentPath(currentPath);
688 while(!paths.empty()) {
689 std::string path = paths.top();
690 if (mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
700 if (tempDirInContainer.compare(pathInContainer) != 0) {
705 std::string parentPathInContainer = parentPath(pathInContainer);
706 ExecFunction createParent = std::bind(createParentDirectories, parentPathInContainer);
709 log_error() <<
"Could not create parent directory " << parentPathInContainer
718 ExecFunction createDir = std::bind(createParentDirectories, pathInContainer);
721 log_error() <<
"Could not create target directory " << pathInContainer
722 <<
" in the container";
726 log_debug() <<
"Touching file in container: " << pathInContainer;
727 bool touchResult =
executeSync([pathInContainer] () {
728 return touch(pathInContainer) ? 0 : 1;
732 log_error() <<
"Could not touch target file " << pathInContainer
733 <<
" in the container";
742 bool mountMoveResult =
executeSync([tempDirInContainer, pathInContainer] () {
743 unsigned long flags = MS_MOVE;
744 int ret = mount(tempDirInContainer.c_str(), pathInContainer.c_str(),
nullptr, flags,
nullptr);
746 printf(
"Error while moving the mount %s to %s: %s\n",
747 tempDirInContainer.c_str(),
748 pathInContainer.c_str(),
754 if (!mountMoveResult) {
755 log_error() <<
"Could not move the mount inside the container: " 756 << tempDirInContainer <<
" to " << pathInContainer;
762 if (readonly && !remountReadOnlyInContainer(pathInContainer)) {
763 log_error() <<
"Failed to remount read only: " << pathInContainer;
770 bool Container::remountReadOnlyInContainer(
const std::string &path)
772 pid_t pid = INVALID_PID;
775 unsigned long flags = MS_REMOUNT | MS_RDONLY | MS_BIND;
776 return mount(path.c_str(), path.c_str(),
"", flags,
nullptr);
780 log_error() <<
"Could not remount " << path <<
" read-only in container";
787 bool Container::mountDevice(
const std::string &pathInHost)
789 if(!ensureContainerRunning()) {
790 log_error() <<
"Container is not running or in bad state, can't mount device: " << pathInHost;
793 log_debug() <<
"Mounting device in container : " << pathInHost;
794 return m_container->add_device_node(m_container, pathInHost.c_str(),
nullptr);
797 bool Container::setEnvironmentVariable(
const std::string &var,
const std::string &val)
799 if (m_state < ContainerState::CREATED) {
800 log_error() <<
"Can't set environment variable for non-created container";
804 log_debug() <<
"Setting env variable in container " << var <<
"=" << val;
805 m_gatewayEnvironmentVariables[var] = val;
808 logging::StringBuilder s;
809 for (
auto &var : m_gatewayEnvironmentVariables) {
810 s <<
"export " << var.first <<
"='" << var.second <<
"'\n";
812 std::string path = buildPath(gatewaysDir(),
"env");
813 FileToolkitWithUndo::writeToFile(path, s);
818 bool Container::suspend()
820 if (m_state < ContainerState::STARTED) {
821 log_error() <<
"Container is not started yet";
825 if (m_state == ContainerState::FROZEN) {
826 log_error() <<
"Container is already suspended";
830 log_debug() <<
"Suspending container";
831 bool retval = m_container->freeze(m_container);
834 std::string errorMessage(
"Could not suspend the container.");
835 log_error() << errorMessage;
839 m_state = ContainerState::FROZEN;
843 bool Container::resume()
845 if (m_state < ContainerState::FROZEN) {
846 log_error() <<
"Container is not suspended";
850 log_debug() <<
"Resuming container";
851 bool retval = m_container->unfreeze(m_container);
854 std::string errorMessage(
"Could not resume the container.");
855 log_error() << errorMessage;
859 m_state = ContainerState::STARTED;
863 const char *Container::id()
const 868 std::string Container::gatewaysDirInContainer()
const 870 return GATEWAYS_PATH;
873 std::string Container::gatewaysDir()
const 875 return buildPath(m_containerRoot, GATEWAYS_PATH);
bool create()
create Creates a new lxc_container and creates it with all the initialization.
Container(const std::string id, const std::string &configFile, const std::string &containerRoot, bool writeBufferEnabled=false, int shutdownTimeout=1)
Constructor.
The FileCleanUpHandler class is a subclass of CleanUpHandler that deletes a file. ...
bool execute(const std::string &commandLine, pid_t *pid, const EnvironmentVariables &variables, const std::string &workingDirectory="/", int stdin=-1, int stdout=1, int stderr=2)
Start a process from the given command line, with an environment consisting of the variables previous...
The CreateDir class is responsible for creating new directories and removing them when it is necessar...
bool stop()
Calls stop on the lxc container(force stop)
bool executeSync(ExecFunction function, pid_t *pid, const EnvironmentVariables &variables=EnvironmentVariables(), int stdin=-1, int stdout=1, int stderr=2)
synchronous version of execute
bool shutdown()
Calls shutdown on the lxc container.
bool bindMountInContainer(const std::string &pathInHost, const std::string &pathInContainer, bool readOnly=true)
Tries to bind mount a path from host to container.
bool start(pid_t *pid)
Start the container.
bool isDirectory(const std::string &path)
isDirectory Check if path is a directory
bool initialize()
Setup the container for startup.
bool existsInFileSystem(const std::string &path)
existsInFileSystem Check if path exists
int waitForProcessTermination(pid_t pid)
waitForProcessTermination Waits for a process to terminate and then returns the status of the process...
bool destroy()
Calls shutdown, and then destroys the container.
Developers guide to adding a config item: