<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <errno.h>
#ifdef __linux__
#include <sched.h>
#endif // __linux__
#include <string.h>

#include <iostream>

#include <process/subprocess.hpp>

#include <stout/foreach.hpp>
#include <stout/os.hpp>
#include <stout/protobuf.hpp>
#include <stout/unreachable.hpp>

#ifdef __linux__
#include "linux/fs.hpp"
#include "linux/ns.hpp"
#endif

#include "mesos/mesos.hpp"

#include "slave/containerizer/mesos/launch.hpp"

using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::vector;

using process::Subprocess;

namespace mesos {
namespace internal {
namespace slave {

const string MesosContainerizerLaunch::NAME = "launch";

MesosContainerizerLaunch::Flags::Flags()
{
  add(&command,
      "command",
      "The command to execute.");

  add(&working_directory,
      "working_directory",
      "The working directory for the command. It has to be an absolute path \n"
      "w.r.t. the root filesystem used for the command.");

#ifndef __WINDOWS__
  add(&rootfs,
      "rootfs",
      "Absolute path to the container root filesystem. The command will be \n"
      "interpreted relative to this path");

  add(&user,
      "user",
      "The user to change to.");
#endif // __WINDOWS__

  add(&pipe_read,
      "pipe_read",
      "The read end of the control pipe. This is a file descriptor \n"
      "on Posix, or a handle on Windows. It's caller's responsibility \n"
      "to make sure the file descriptor or the handle is inherited \n"
      "properly in the subprocess. It's used to synchronize with the \n"
      "parent process. If not specified, no synchronization will happen.");

  add(&pipe_write,
      "pipe_write",
      "The write end of the control pipe. This is a file descriptor \n"
      "on Posix, or a handle on Windows. It's caller's responsibility \n"
      "to make sure the file descriptor or the handle is inherited \n"
      "properly in the subprocess. It's used to synchronize with the \n"
      "parent process. If not specified, no synchronization will happen.");

  add(&pre_exec_commands,
      "pre_exec_commands",
      "The additional preparation commands to execute before\n"
      "executing the command.");

#ifdef __linux__
  add(&unshare_namespace_mnt,
      "unshare_namespace_mnt",
      "Whether to launch the command in a new mount namespace.",
      false);
#endif // __linux__
}

int MesosContainerizerLaunch::execute()
{
  // Check command line flags.
  if (flags.command.isNone()) {
    cerr << "Flag --command is not specified" << endl;
    return EXIT_FAILURE;
  }

  bool controlPipeSpecified =
    flags.pipe_read.isSome() && flags.pipe_write.isSome();

  if ((flags.pipe_read.isSome() && flags.pipe_write.isNone()) ||
      (flags.pipe_read.isNone() && flags.pipe_write.isSome())) {
    cerr << "Flag --pipe_read and --pipe_write should either be "
         << "both set or both not set" << endl;
    return EXIT_FAILURE;
  }

  // Parse the command.
  Try<CommandInfo> command =
    ::protobuf::parse<CommandInfo>(flags.command.get());

  if (command.isError()) {
    cerr << "Failed to parse the command: " << command.error() << endl;
    return EXIT_FAILURE;
  }

  // Validate the command.
  if (command.get().shell()) {
    if (!command.get().has_value()) {
      cerr << "Shell command is not specified" << endl;
      return EXIT_FAILURE;
    }
  } else {
    if (!command.get().has_value()) {
      cerr << "Executable path is not specified" << endl;
      return EXIT_FAILURE;
    }
  }

  if (controlPipeSpecified) {
    int pipe[2] = { flags.pipe_read.get(), flags.pipe_write.get() };

    // NOTE: On windows we need to pass `HANDLE`s between processes,
    // as file descriptors are not unique across processes. Here we
    // convert back from from the `HANDLE`s we receive to fds that can
    // be used in os-agnostic code.
#ifdef __WINDOWS__
    pipe[0] = os::handle_to_fd(pipe[0], _O_RDONLY | _O_TEXT);
    pipe[1] = os::handle_to_fd(pipe[1], _O_TEXT);
#endif // __WINDOWS__

    Try<Nothing> close = os::close(pipe[1]);
    if (close.isError()) {
      cerr << "Failed to close pipe[1]: " << close.error() << endl;
      return EXIT_FAILURE;
    }

    // Do a blocking read on the pipe until the parent signals us to continue.
    char dummy;
    ssize_t length;
    while ((length = os::read(pipe[0], &dummy, sizeof(dummy))) == -1 &&
           errno == EINTR);

    if (length != sizeof(dummy)) {
       // There's a reasonable probability this will occur during
       // agent restarts across a large/busy cluster.
       cerr << "Failed to synchronize with agent "
            << "(it's probably exited)" << endl;
       return EXIT_FAILURE;
    }

    close = os::close(pipe[0]);
    if (close.isError()) {
      cerr << "Failed to close pipe[0]: " << close.error() << endl;
      return EXIT_FAILURE;
    }
  }

#ifdef __linux__
  if (flags.unshare_namespace_mnt) {
    if (unshare(CLONE_NEWNS) != 0) {
      cerr << "Failed to unshare mount namespace: "
           << os::strerror(errno) << endl;
      return EXIT_FAILURE;
    }
  }
#endif // __linux__

  // Run additional preparation commands. These are run as the same
  // user and with the environment as the agent.
  if (flags.pre_exec_commands.isSome()) {
    // TODO(jieyu): Use JSON::Array if we have generic parse support.
    JSON::Array array = flags.pre_exec_commands.get();
    foreach (const JSON::Value& value, array.values) {
      if (!value.is<JSON::Object>()) {
        cerr << "Invalid JSON format for flag --commands" << endl;
        return EXIT_FAILURE;
      }

      Try<CommandInfo> parse = ::protobuf::parse<CommandInfo>(value);
      if (parse.isError()) {
        cerr << "Failed to parse a preparation command: "
             << parse.error() << endl;
        return EXIT_FAILURE;
      }

      if (!parse.get().has_value()) {
        cerr << "The 'value' of a preparation command is not specified" << endl;
        return EXIT_FAILURE;
      }

      Try<Subprocess> s = Error("Not launched");

      if (parse->shell()) {
        s = subprocess(parse->value(), Subprocess::PATH("/dev/null"));
      } else {
        // Launch non-shell command as a subprocess to avoid injecting
        // arbitrary shell commands.
        vector<string> args;
        foreach (const string& arg, parse->arguments()) {
          args.push_back(arg);
        }

        s = subprocess(parse->value(), args, Subprocess::PATH("/dev/null"));
      }

      if (s.isError()) {
        cerr << "Failed to create the pre-exec subprocess: "
             << s.error() << endl;
        return EXIT_FAILURE;
      }

      s->status().await();

      Option<int> status = s->status().get();
      if (status.isNone()) {
        cerr << "Failed to reap the pre-exec subprocess "
             << "'" << value << "'" << endl;
        return EXIT_FAILURE;
      } else if (status.get() != 0) {
        cerr << "The pre-exec subprocess '" << value << "' "
             << "failed" << endl;
        return EXIT_FAILURE;
      }
    }
  }

#ifndef __WINDOWS__
  // NOTE: If 'flags.user' is set, we will get the uid, gid, and the
  // supplementary group ids associated with the specified user before
  // changing the filesystem root. This is because after changing the
  // filesystem root, the current process might no longer have access
  // to /etc/passwd and /etc/group on the host.
  Option<uid_t> uid;
  Option<gid_t> gid;
  vector<gid_t> gids;

  // TODO(gilbert): For the case container user exists, support
  // framework/task/default user -> container user mapping once
  // user namespace and container capabilities is available for
  // mesos container.

  if (flags.user.isSome()) {
    Result<uid_t> _uid = os::getuid(flags.user.get());
    if (!_uid.isSome()) {
      cerr << "Failed to get the uid of user '" << flags.user.get() << "': "
           << (_uid.isError() ? _uid.error() : "not found") << endl;
      return EXIT_FAILURE;
    }

    // No need to change user/groups if the specified user is the same
    // as that of the current process.
    if (_uid.get() != os::getuid().get()) {
      Result<gid_t> _gid = os::getgid(flags.user.get());
      if (!_gid.isSome()) {
        cerr << "Failed to get the gid of user '" << flags.user.get() << "': "
             << (_gid.isError() ? _gid.error() : "not found") << endl;
        return EXIT_FAILURE;
      }

      Try<vector<gid_t>> _gids = os::getgrouplist(flags.user.get());
      if (_gids.isError()) {
        cerr << "Failed to get the supplementary gids of user '"
             << flags.user.get() << "': "
             << (_gids.isError() ? _gids.error() : "not found") << endl;
        return EXIT_FAILURE;
      }

      uid = _uid.get();
      gid = _gid.get();
      gids = _gids.get();
    }
  }
#endif // __WINDOWS__

#ifdef __WINDOWS__
  // Not supported on Windows.
  const Option<std::string> rootfs = None();
#else
  const Option<std::string> rootfs = flags.rootfs;
#endif // __WINDOWS__

  // Change root to a new root, if provided.
  if (rootfs.isSome()) {
    cout << "Changing root to " << rootfs.get() << endl;

    // Verify that rootfs is an absolute path.
    Result<string> realpath = os::realpath(rootfs.get());
    if (realpath.isError()) {
      cerr << "Failed to determine if rootfs is an absolute path: "
           << realpath.error() << endl;
      return EXIT_FAILURE;
    } else if (realpath.isNone()) {
      cerr << "Rootfs path does not exist" << endl;
      return EXIT_FAILURE;
    } else if (realpath.get() != rootfs.get()) {
      cerr << "Rootfs path is not an absolute path" << endl;
      return EXIT_FAILURE;
    }

#ifdef __linux__
    Try<Nothing> chroot = fs::chroot::enter(rootfs.get());
#elif defined(__WINDOWS__)
    Try<Nothing> chroot = Error("`chroot` not supported on Windows");
#else // For any other platform we'll just use POSIX chroot.
    Try<Nothing> chroot = os::chroot(rootfs.get());
#endif // __linux__
    if (chroot.isError()) {
      cerr << "Failed to enter chroot '" << rootfs.get()
           << "': " << chroot.error();
      return EXIT_FAILURE;
    }
  }

  // Change user if provided. Note that we do that after executing the
  // preparation commands so that those commands will be run with the
  // same privilege as the mesos-agent.
#ifndef __WINDOWS__
  if (uid.isSome()) {
    Try<Nothing> setgid = os::setgid(gid.get());
    if (setgid.isError()) {
      cerr << "Failed to set gid to " << gid.get()
           << ": " << setgid.error() << endl;
      return EXIT_FAILURE;
    }

    Try<Nothing> setgroups = os::setgroups(gids, uid);
    if (setgroups.isError()) {
      cerr << "Failed to set supplementary gids: "
           << setgroups.error() << endl;
      return EXIT_FAILURE;
    }

    Try<Nothing> setuid = os::setuid(uid.get());
    if (setuid.isError()) {
      cerr << "Failed to set uid to " << uid.get()
           << ": " << setuid.error() << endl;
      return EXIT_FAILURE;
    }
  }
#endif // __WINDOWS__

  if (flags.working_directory.isSome()) {
    Try<Nothing> chdir = os::chdir(flags.working_directory.get());
    if (chdir.isError()) {
      cerr << "Failed to chdir into current working directory "
           << "'" << flags.working_directory.get() << "': "
           << chdir.error() << endl;
      return EXIT_FAILURE;
    }
  }

  // Relay the environment variables.
  // TODO(jieyu): Consider using a clean environment.

  if (command->shell()) {
    // Execute the command using shell.
    os::execlp(os::Shell::name,
               os::Shell::arg0,
               os::Shell::arg1,
               command->value().c_str(),
               (char*) nullptr);
  } else {
    // Use execvp to launch the command.
    execvp(command->value().c_str(),
           os::raw::Argv(command->arguments()));
  }

  // If we get here, the execle call failed.
  cerr << "Failed to execute command: " << os::strerror(errno) << endl;
  UNREACHABLE();
}

} // namespace slave {
} // namespace internal {
} // namespace mesos {
<commit_msg>Added logs for pre-exec commands to sandbox in MesosContainerizerLaunch.<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <errno.h>
#ifdef __linux__
#include <sched.h>
#endif // __linux__
#include <string.h>

#include <iostream>

#include <process/subprocess.hpp>

#include <stout/foreach.hpp>
#include <stout/os.hpp>
#include <stout/protobuf.hpp>
#include <stout/unreachable.hpp>

#ifdef __linux__
#include "linux/fs.hpp"
#include "linux/ns.hpp"
#endif

#include "mesos/mesos.hpp"

#include "slave/containerizer/mesos/launch.hpp"

using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::vector;

using process::Subprocess;

namespace mesos {
namespace internal {
namespace slave {

const string MesosContainerizerLaunch::NAME = "launch";

MesosContainerizerLaunch::Flags::Flags()
{
  add(&command,
      "command",
      "The command to execute.");

  add(&working_directory,
      "working_directory",
      "The working directory for the command. It has to be an absolute path \n"
      "w.r.t. the root filesystem used for the command.");

#ifndef __WINDOWS__
  add(&rootfs,
      "rootfs",
      "Absolute path to the container root filesystem. The command will be \n"
      "interpreted relative to this path");

  add(&user,
      "user",
      "The user to change to.");
#endif // __WINDOWS__

  add(&pipe_read,
      "pipe_read",
      "The read end of the control pipe. This is a file descriptor \n"
      "on Posix, or a handle on Windows. It's caller's responsibility \n"
      "to make sure the file descriptor or the handle is inherited \n"
      "properly in the subprocess. It's used to synchronize with the \n"
      "parent process. If not specified, no synchronization will happen.");

  add(&pipe_write,
      "pipe_write",
      "The write end of the control pipe. This is a file descriptor \n"
      "on Posix, or a handle on Windows. It's caller's responsibility \n"
      "to make sure the file descriptor or the handle is inherited \n"
      "properly in the subprocess. It's used to synchronize with the \n"
      "parent process. If not specified, no synchronization will happen.");

  add(&pre_exec_commands,
      "pre_exec_commands",
      "The additional preparation commands to execute before\n"
      "executing the command.");

#ifdef __linux__
  add(&unshare_namespace_mnt,
      "unshare_namespace_mnt",
      "Whether to launch the command in a new mount namespace.",
      false);
#endif // __linux__
}

int MesosContainerizerLaunch::execute()
{
  // Check command line flags.
  if (flags.command.isNone()) {
    cerr << "Flag --command is not specified" << endl;
    return EXIT_FAILURE;
  }

  bool controlPipeSpecified =
    flags.pipe_read.isSome() && flags.pipe_write.isSome();

  if ((flags.pipe_read.isSome() && flags.pipe_write.isNone()) ||
      (flags.pipe_read.isNone() && flags.pipe_write.isSome())) {
    cerr << "Flag --pipe_read and --pipe_write should either be "
         << "both set or both not set" << endl;
    return EXIT_FAILURE;
  }

  // Parse the command.
  Try<CommandInfo> command =
    ::protobuf::parse<CommandInfo>(flags.command.get());

  if (command.isError()) {
    cerr << "Failed to parse the command: " << command.error() << endl;
    return EXIT_FAILURE;
  }

  // Validate the command.
  if (command.get().shell()) {
    if (!command.get().has_value()) {
      cerr << "Shell command is not specified" << endl;
      return EXIT_FAILURE;
    }
  } else {
    if (!command.get().has_value()) {
      cerr << "Executable path is not specified" << endl;
      return EXIT_FAILURE;
    }
  }

  if (controlPipeSpecified) {
    int pipe[2] = { flags.pipe_read.get(), flags.pipe_write.get() };

    // NOTE: On windows we need to pass `HANDLE`s between processes,
    // as file descriptors are not unique across processes. Here we
    // convert back from from the `HANDLE`s we receive to fds that can
    // be used in os-agnostic code.
#ifdef __WINDOWS__
    pipe[0] = os::handle_to_fd(pipe[0], _O_RDONLY | _O_TEXT);
    pipe[1] = os::handle_to_fd(pipe[1], _O_TEXT);
#endif // __WINDOWS__

    Try<Nothing> close = os::close(pipe[1]);
    if (close.isError()) {
      cerr << "Failed to close pipe[1]: " << close.error() << endl;
      return EXIT_FAILURE;
    }

    // Do a blocking read on the pipe until the parent signals us to continue.
    char dummy;
    ssize_t length;
    while ((length = os::read(pipe[0], &dummy, sizeof(dummy))) == -1 &&
           errno == EINTR);

    if (length != sizeof(dummy)) {
       // There's a reasonable probability this will occur during
       // agent restarts across a large/busy cluster.
       cerr << "Failed to synchronize with agent "
            << "(it's probably exited)" << endl;
       return EXIT_FAILURE;
    }

    close = os::close(pipe[0]);
    if (close.isError()) {
      cerr << "Failed to close pipe[0]: " << close.error() << endl;
      return EXIT_FAILURE;
    }
  }

#ifdef __linux__
  if (flags.unshare_namespace_mnt) {
    if (unshare(CLONE_NEWNS) != 0) {
      cerr << "Failed to unshare mount namespace: "
           << os::strerror(errno) << endl;
      return EXIT_FAILURE;
    }
  }
#endif // __linux__

  // Run additional preparation commands. These are run as the same
  // user and with the environment as the agent.
  if (flags.pre_exec_commands.isSome()) {
    // TODO(jieyu): Use JSON::Array if we have generic parse support.
    JSON::Array array = flags.pre_exec_commands.get();
    foreach (const JSON::Value& value, array.values) {
      if (!value.is<JSON::Object>()) {
        cerr << "Invalid JSON format for flag --commands" << endl;
        return EXIT_FAILURE;
      }

      Try<CommandInfo> parse = ::protobuf::parse<CommandInfo>(value);
      if (parse.isError()) {
        cerr << "Failed to parse a preparation command: "
             << parse.error() << endl;
        return EXIT_FAILURE;
      }

      if (!parse.get().has_value()) {
        cerr << "The 'value' of a preparation command is not specified" << endl;
        return EXIT_FAILURE;
      }

      cout << "Executing pre-exec command '" << value << "'" << endl;

      Try<Subprocess> s = Error("Not launched");

      if (parse->shell()) {
        s = subprocess(parse->value(), Subprocess::PATH("/dev/null"));
      } else {
        // Launch non-shell command as a subprocess to avoid injecting
        // arbitrary shell commands.
        vector<string> args;
        foreach (const string& arg, parse->arguments()) {
          args.push_back(arg);
        }

        s = subprocess(parse->value(), args, Subprocess::PATH("/dev/null"));
      }

      if (s.isError()) {
        cerr << "Failed to create the pre-exec subprocess: "
             << s.error() << endl;
        return EXIT_FAILURE;
      }

      s->status().await();

      Option<int> status = s->status().get();
      if (status.isNone()) {
        cerr << "Failed to reap the pre-exec subprocess "
             << "'" << value << "'" << endl;
        return EXIT_FAILURE;
      } else if (status.get() != 0) {
        cerr << "The pre-exec subprocess '" << value << "' "
             << "failed" << endl;
        return EXIT_FAILURE;
      }
    }
  }

#ifndef __WINDOWS__
  // NOTE: If 'flags.user' is set, we will get the uid, gid, and the
  // supplementary group ids associated with the specified user before
  // changing the filesystem root. This is because after changing the
  // filesystem root, the current process might no longer have access
  // to /etc/passwd and /etc/group on the host.
  Option<uid_t> uid;
  Option<gid_t> gid;
  vector<gid_t> gids;

  // TODO(gilbert): For the case container user exists, support
  // framework/task/default user -> container user mapping once
  // user namespace and container capabilities is available for
  // mesos container.

  if (flags.user.isSome()) {
    Result<uid_t> _uid = os::getuid(flags.user.get());
    if (!_uid.isSome()) {
      cerr << "Failed to get the uid of user '" << flags.user.get() << "': "
           << (_uid.isError() ? _uid.error() : "not found") << endl;
      return EXIT_FAILURE;
    }

    // No need to change user/groups if the specified user is the same
    // as that of the current process.
    if (_uid.get() != os::getuid().get()) {
      Result<gid_t> _gid = os::getgid(flags.user.get());
      if (!_gid.isSome()) {
        cerr << "Failed to get the gid of user '" << flags.user.get() << "': "
             << (_gid.isError() ? _gid.error() : "not found") << endl;
        return EXIT_FAILURE;
      }

      Try<vector<gid_t>> _gids = os::getgrouplist(flags.user.get());
      if (_gids.isError()) {
        cerr << "Failed to get the supplementary gids of user '"
             << flags.user.get() << "': "
             << (_gids.isError() ? _gids.error() : "not found") << endl;
        return EXIT_FAILURE;
      }

      uid = _uid.get();
      gid = _gid.get();
      gids = _gids.get();
    }
  }
#endif // __WINDOWS__

#ifdef __WINDOWS__
  // Not supported on Windows.
  const Option<std::string> rootfs = None();
#else
  const Option<std::string> rootfs = flags.rootfs;
#endif // __WINDOWS__

  // Change root to a new root, if provided.
  if (rootfs.isSome()) {
    cout << "Changing root to " << rootfs.get() << endl;

    // Verify that rootfs is an absolute path.
    Result<string> realpath = os::realpath(rootfs.get());
    if (realpath.isError()) {
      cerr << "Failed to determine if rootfs is an absolute path: "
           << realpath.error() << endl;
      return EXIT_FAILURE;
    } else if (realpath.isNone()) {
      cerr << "Rootfs path does not exist" << endl;
      return EXIT_FAILURE;
    } else if (realpath.get() != rootfs.get()) {
      cerr << "Rootfs path is not an absolute path" << endl;
      return EXIT_FAILURE;
    }

#ifdef __linux__
    Try<Nothing> chroot = fs::chroot::enter(rootfs.get());
#elif defined(__WINDOWS__)
    Try<Nothing> chroot = Error("`chroot` not supported on Windows");
#else // For any other platform we'll just use POSIX chroot.
    Try<Nothing> chroot = os::chroot(rootfs.get());
#endif // __linux__
    if (chroot.isError()) {
      cerr << "Failed to enter chroot '" << rootfs.get()
           << "': " << chroot.error();
      return EXIT_FAILURE;
    }
  }

  // Change user if provided. Note that we do that after executing the
  // preparation commands so that those commands will be run with the
  // same privilege as the mesos-agent.
#ifndef __WINDOWS__
  if (uid.isSome()) {
    Try<Nothing> setgid = os::setgid(gid.get());
    if (setgid.isError()) {
      cerr << "Failed to set gid to " << gid.get()
           << ": " << setgid.error() << endl;
      return EXIT_FAILURE;
    }

    Try<Nothing> setgroups = os::setgroups(gids, uid);
    if (setgroups.isError()) {
      cerr << "Failed to set supplementary gids: "
           << setgroups.error() << endl;
      return EXIT_FAILURE;
    }

    Try<Nothing> setuid = os::setuid(uid.get());
    if (setuid.isError()) {
      cerr << "Failed to set uid to " << uid.get()
           << ": " << setuid.error() << endl;
      return EXIT_FAILURE;
    }
  }
#endif // __WINDOWS__

  if (flags.working_directory.isSome()) {
    Try<Nothing> chdir = os::chdir(flags.working_directory.get());
    if (chdir.isError()) {
      cerr << "Failed to chdir into current working directory "
           << "'" << flags.working_directory.get() << "': "
           << chdir.error() << endl;
      return EXIT_FAILURE;
    }
  }

  // Relay the environment variables.
  // TODO(jieyu): Consider using a clean environment.

  if (command->shell()) {
    // Execute the command using shell.
    os::execlp(os::Shell::name,
               os::Shell::arg0,
               os::Shell::arg1,
               command->value().c_str(),
               (char*) nullptr);
  } else {
    // Use execvp to launch the command.
    execvp(command->value().c_str(),
           os::raw::Argv(command->arguments()));
  }

  // If we get here, the execle call failed.
  cerr << "Failed to execute command: " << os::strerror(errno) << endl;
  UNREACHABLE();
}

} // namespace slave {
} // namespace internal {
} // namespace mesos {
<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <errno.h>
#ifdef __linux__
#include <sched.h>
#endif // __linux__
#include <string.h>

#include <iostream>

#include <process/subprocess.hpp>

#include <stout/foreach.hpp>
#include <stout/os.hpp>
#include <stout/protobuf.hpp>
#include <stout/unreachable.hpp>

#ifdef __linux__
#include "linux/fs.hpp"
#include "linux/ns.hpp"
#endif

#include "mesos/mesos.hpp"

#include "slave/containerizer/mesos/launch.hpp"

using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::vector;

using process::Subprocess;

namespace mesos {
namespace internal {
namespace slave {

const string MesosContainerizerLaunch::NAME = "launch";

MesosContainerizerLaunch::Flags::Flags()
{
  add(&command,
      "command",
      "The command to execute.");

  add(&working_directory,
      "working_directory",
      "The working directory for the command. It has to be an absolute path \n"
      "w.r.t. the root filesystem used for the command.");

#ifndef __WINDOWS__
  add(&rootfs,
      "rootfs",
      "Absolute path to the container root filesystem. The command will be \n"
      "interpreted relative to this path");

  add(&user,
      "user",
      "The user to change to.");
#endif // __WINDOWS__

  add(&pipe_read,
      "pipe_read",
      "The read end of the control pipe. This is a file descriptor \n"
      "on Posix, or a handle on Windows. It's caller's responsibility \n"
      "to make sure the file descriptor or the handle is inherited \n"
      "properly in the subprocess. It's used to synchronize with the \n"
      "parent process. If not specified, no synchronization will happen.");

  add(&pipe_write,
      "pipe_write",
      "The write end of the control pipe. This is a file descriptor \n"
      "on Posix, or a handle on Windows. It's caller's responsibility \n"
      "to make sure the file descriptor or the handle is inherited \n"
      "properly in the subprocess. It's used to synchronize with the \n"
      "parent process. If not specified, no synchronization will happen.");

  add(&pre_exec_commands,
      "pre_exec_commands",
      "The additional preparation commands to execute before\n"
      "executing the command.");

#ifdef __linux__
  add(&unshare_namespace_mnt,
      "unshare_namespace_mnt",
      "Whether to launch the command in a new mount namespace.",
      false);
#endif // __linux__
}

int MesosContainerizerLaunch::execute()
{
  // Check command line flags.
  if (flags.command.isNone()) {
    cerr << "Flag --command is not specified" << endl;
    return 1;
  }

  bool controlPipeSpecified =
    flags.pipe_read.isSome() && flags.pipe_write.isSome();

  if ((flags.pipe_read.isSome() && flags.pipe_write.isNone()) ||
      (flags.pipe_read.isNone() && flags.pipe_write.isSome())) {
    cerr << "Flag --pipe_read and --pipe_write should either be "
         << "both set or both not set" << endl;
    return 1;
  }

  // Parse the command.
  Try<CommandInfo> command =
    ::protobuf::parse<CommandInfo>(flags.command.get());

  if (command.isError()) {
    cerr << "Failed to parse the command: " << command.error() << endl;
    return 1;
  }

  // Validate the command.
  if (command.get().shell()) {
    if (!command.get().has_value()) {
      cerr << "Shell command is not specified" << endl;
      return 1;
    }
  } else {
    if (!command.get().has_value()) {
      cerr << "Executable path is not specified" << endl;
      return 1;
    }
  }

  if (controlPipeSpecified) {
    int pipe[2] = { flags.pipe_read.get(), flags.pipe_write.get() };

    // NOTE: On windows we need to pass `HANDLE`s between processes,
    // as file descriptors are not unique across processes. Here we
    // convert back from from the `HANDLE`s we receive to fds that can
    // be used in os-agnostic code.
#ifdef __WINDOWS__
    pipe[0] = os::handle_to_fd(pipe[0], _O_RDONLY | _O_TEXT);
    pipe[1] = os::handle_to_fd(pipe[1], _O_TEXT);
#endif // __WINDOWS__

    Try<Nothing> close = os::close(pipe[1]);
    if (close.isError()) {
      cerr << "Failed to close pipe[1]: " << close.error() << endl;
      return 1;
    }

    // Do a blocking read on the pipe until the parent signals us to continue.
    char dummy;
    ssize_t length;
    while ((length = os::read(pipe[0], &dummy, sizeof(dummy))) == -1 &&
           errno == EINTR);

    if (length != sizeof(dummy)) {
       // There's a reasonable probability this will occur during
       // agent restarts across a large/busy cluster.
       cerr << "Failed to synchronize with agent "
            << "(it's probably exited)" << endl;
       return 1;
    }

    close = os::close(pipe[0]);
    if (close.isError()) {
      cerr << "Failed to close pipe[0]: " << close.error() << endl;
      return 1;
    }
  }

#ifdef __linux__
  if (flags.unshare_namespace_mnt) {
    if (unshare(CLONE_NEWNS) != 0) {
      cerr << "Failed to unshare mount namespace: "
           << os::strerror(errno) << endl;
      return 1;
    }
  }
#endif // __linux__

  // Run additional preparation commands. These are run as the same
  // user and with the environment as the agent.
  if (flags.pre_exec_commands.isSome()) {
    // TODO(jieyu): Use JSON::Array if we have generic parse support.
    JSON::Array array = flags.pre_exec_commands.get();
    foreach (const JSON::Value& value, array.values) {
      if (!value.is<JSON::Object>()) {
        cerr << "Invalid JSON format for flag --commands" << endl;
        return 1;
      }

      Try<CommandInfo> parse = ::protobuf::parse<CommandInfo>(value);
      if (parse.isError()) {
        cerr << "Failed to parse a preparation command: "
             << parse.error() << endl;
        return 1;
      }

      if (!parse.get().has_value()) {
        cerr << "The 'value' of a preparation command is not specified" << endl;
        return 1;
      }

      Try<Subprocess> s = Error("Not launched");

      if (parse->shell()) {
        s = subprocess(parse->value(), Subprocess::PATH("/dev/null"));
      } else {
        // Launch non-shell command as a subprocess to avoid injecting
        // arbitrary shell commands.
        vector<string> args;
        foreach (const string& arg, parse->arguments()) {
          args.push_back(arg);
        }

        s = subprocess(parse->value(), args, Subprocess::PATH("/dev/null"));
      }

      if (s.isError()) {
        cerr << "Failed to create the pre-exec subprocess: "
             << s.error() << endl;
        return EXIT_FAILURE;
      }

      s->status().await();

      Option<int> status = s->status().get();
      if (status.isNone()) {
        cerr << "Failed to reap the pre-exec subprocess "
             << "'" << value << "'" << endl;
        return EXIT_FAILURE;
      } else if (status.get() != 0) {
        cerr << "The pre-exec subprocess '" << value << "' "
             << "failed" << endl;
        return EXIT_FAILURE;
      }
    }
  }

#ifndef __WINDOWS__
  // NOTE: If 'flags.user' is set, we will get the uid, gid, and the
  // supplementary group ids associated with the specified user before
  // changing the filesystem root. This is because after changing the
  // filesystem root, the current process might no longer have access
  // to /etc/passwd and /etc/group on the host.
  Option<uid_t> uid;
  Option<gid_t> gid;
  vector<gid_t> gids;

  // TODO(gilbert): For the case container user exists, support
  // framework/task/default user -> container user mapping once
  // user namespace and container capabilities is available for
  // mesos container.

  if (flags.user.isSome()) {
    Result<uid_t> _uid = os::getuid(flags.user.get());
    if (!_uid.isSome()) {
      cerr << "Failed to get the uid of user '" << flags.user.get() << "': "
           << (_uid.isError() ? _uid.error() : "not found") << endl;
      return 1;
    }

    // No need to change user/groups if the specified user is the same
    // as that of the current process.
    if (_uid.get() != os::getuid().get()) {
      Result<gid_t> _gid = os::getgid(flags.user.get());
      if (!_gid.isSome()) {
        cerr << "Failed to get the gid of user '" << flags.user.get() << "': "
             << (_gid.isError() ? _gid.error() : "not found") << endl;
        return 1;
      }

      Try<vector<gid_t>> _gids = os::getgrouplist(flags.user.get());
      if (_gids.isError()) {
        cerr << "Failed to get the supplementary gids of user '"
             << flags.user.get() << "': "
             << (_gids.isError() ? _gids.error() : "not found") << endl;
        return 1;
      }

      uid = _uid.get();
      gid = _gid.get();
      gids = _gids.get();
    }
  }
#endif // __WINDOWS__

#ifdef __WINDOWS__
  // Not supported on Windows.
  const Option<std::string> rootfs = None();
#else
  const Option<std::string> rootfs = flags.rootfs;
#endif // __WINDOWS__

  // Change root to a new root, if provided.
  if (rootfs.isSome()) {
    cout << "Changing root to " << rootfs.get() << endl;

    // Verify that rootfs is an absolute path.
    Result<string> realpath = os::realpath(rootfs.get());
    if (realpath.isError()) {
      cerr << "Failed to determine if rootfs is an absolute path: "
           << realpath.error() << endl;
      return 1;
    } else if (realpath.isNone()) {
      cerr << "Rootfs path does not exist" << endl;
      return 1;
    } else if (realpath.get() != rootfs.get()) {
      cerr << "Rootfs path is not an absolute path" << endl;
      return 1;
    }

#ifdef __linux__
    Try<Nothing> chroot = fs::chroot::enter(rootfs.get());
#elif defined(__WINDOWS__)
    Try<Nothing> chroot = Error("`chroot` not supported on Windows");
#else // For any other platform we'll just use POSIX chroot.
    Try<Nothing> chroot = os::chroot(rootfs.get());
#endif // __linux__
    if (chroot.isError()) {
      cerr << "Failed to enter chroot '" << rootfs.get()
           << "': " << chroot.error();
      return 1;
    }
  }

  // Change user if provided. Note that we do that after executing the
  // preparation commands so that those commands will be run with the
  // same privilege as the mesos-agent.
#ifndef __WINDOWS__
  if (uid.isSome()) {
    Try<Nothing> setgid = os::setgid(gid.get());
    if (setgid.isError()) {
      cerr << "Failed to set gid to " << gid.get()
           << ": " << setgid.error() << endl;
      return 1;
    }

    Try<Nothing> setgroups = os::setgroups(gids, uid);
    if (setgroups.isError()) {
      cerr << "Failed to set supplementary gids: "
           << setgroups.error() << endl;
      return 1;
    }

    Try<Nothing> setuid = os::setuid(uid.get());
    if (setuid.isError()) {
      cerr << "Failed to set uid to " << uid.get()
           << ": " << setuid.error() << endl;
      return 1;
    }
  }
#endif // __WINDOWS__

  if (flags.working_directory.isSome()) {
    Try<Nothing> chdir = os::chdir(flags.working_directory.get());
    if (chdir.isError()) {
      cerr << "Failed to chdir into current working directory "
           << "'" << flags.working_directory.get() << "': "
           << chdir.error() << endl;
      return 1;
    }
  }

  // Relay the environment variables.
  // TODO(jieyu): Consider using a clean environment.

  if (command->shell()) {
    // Execute the command using shell.
    os::execlp(os::Shell::name,
               os::Shell::arg0,
               os::Shell::arg1,
               command->value().c_str(),
               (char*) nullptr);
  } else {
    // Use execvp to launch the command.
    execvp(command->value().c_str(),
           os::raw::Argv(command->arguments()));
  }

  // If we get here, the execle call failed.
  cerr << "Failed to execute command: " << os::strerror(errno) << endl;
  UNREACHABLE();
}

} // namespace slave {
} // namespace internal {
} // namespace mesos {
<commit_msg>Updated mesos containerizer launch execute() to return 'EXIT_FAILURE'.<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <errno.h>
#ifdef __linux__
#include <sched.h>
#endif // __linux__
#include <string.h>

#include <iostream>

#include <process/subprocess.hpp>

#include <stout/foreach.hpp>
#include <stout/os.hpp>
#include <stout/protobuf.hpp>
#include <stout/unreachable.hpp>

#ifdef __linux__
#include "linux/fs.hpp"
#include "linux/ns.hpp"
#endif

#include "mesos/mesos.hpp"

#include "slave/containerizer/mesos/launch.hpp"

using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::vector;

using process::Subprocess;

namespace mesos {
namespace internal {
namespace slave {

const string MesosContainerizerLaunch::NAME = "launch";

MesosContainerizerLaunch::Flags::Flags()
{
  add(&command,
      "command",
      "The command to execute.");

  add(&working_directory,
      "working_directory",
      "The working directory for the command. It has to be an absolute path \n"
      "w.r.t. the root filesystem used for the command.");

#ifndef __WINDOWS__
  add(&rootfs,
      "rootfs",
      "Absolute path to the container root filesystem. The command will be \n"
      "interpreted relative to this path");

  add(&user,
      "user",
      "The user to change to.");
#endif // __WINDOWS__

  add(&pipe_read,
      "pipe_read",
      "The read end of the control pipe. This is a file descriptor \n"
      "on Posix, or a handle on Windows. It's caller's responsibility \n"
      "to make sure the file descriptor or the handle is inherited \n"
      "properly in the subprocess. It's used to synchronize with the \n"
      "parent process. If not specified, no synchronization will happen.");

  add(&pipe_write,
      "pipe_write",
      "The write end of the control pipe. This is a file descriptor \n"
      "on Posix, or a handle on Windows. It's caller's responsibility \n"
      "to make sure the file descriptor or the handle is inherited \n"
      "properly in the subprocess. It's used to synchronize with the \n"
      "parent process. If not specified, no synchronization will happen.");

  add(&pre_exec_commands,
      "pre_exec_commands",
      "The additional preparation commands to execute before\n"
      "executing the command.");

#ifdef __linux__
  add(&unshare_namespace_mnt,
      "unshare_namespace_mnt",
      "Whether to launch the command in a new mount namespace.",
      false);
#endif // __linux__
}

int MesosContainerizerLaunch::execute()
{
  // Check command line flags.
  if (flags.command.isNone()) {
    cerr << "Flag --command is not specified" << endl;
    return EXIT_FAILURE;
  }

  bool controlPipeSpecified =
    flags.pipe_read.isSome() && flags.pipe_write.isSome();

  if ((flags.pipe_read.isSome() && flags.pipe_write.isNone()) ||
      (flags.pipe_read.isNone() && flags.pipe_write.isSome())) {
    cerr << "Flag --pipe_read and --pipe_write should either be "
         << "both set or both not set" << endl;
    return EXIT_FAILURE;
  }

  // Parse the command.
  Try<CommandInfo> command =
    ::protobuf::parse<CommandInfo>(flags.command.get());

  if (command.isError()) {
    cerr << "Failed to parse the command: " << command.error() << endl;
    return EXIT_FAILURE;
  }

  // Validate the command.
  if (command.get().shell()) {
    if (!command.get().has_value()) {
      cerr << "Shell command is not specified" << endl;
      return EXIT_FAILURE;
    }
  } else {
    if (!command.get().has_value()) {
      cerr << "Executable path is not specified" << endl;
      return EXIT_FAILURE;
    }
  }

  if (controlPipeSpecified) {
    int pipe[2] = { flags.pipe_read.get(), flags.pipe_write.get() };

    // NOTE: On windows we need to pass `HANDLE`s between processes,
    // as file descriptors are not unique across processes. Here we
    // convert back from from the `HANDLE`s we receive to fds that can
    // be used in os-agnostic code.
#ifdef __WINDOWS__
    pipe[0] = os::handle_to_fd(pipe[0], _O_RDONLY | _O_TEXT);
    pipe[1] = os::handle_to_fd(pipe[1], _O_TEXT);
#endif // __WINDOWS__

    Try<Nothing> close = os::close(pipe[1]);
    if (close.isError()) {
      cerr << "Failed to close pipe[1]: " << close.error() << endl;
      return EXIT_FAILURE;
    }

    // Do a blocking read on the pipe until the parent signals us to continue.
    char dummy;
    ssize_t length;
    while ((length = os::read(pipe[0], &dummy, sizeof(dummy))) == -1 &&
           errno == EINTR);

    if (length != sizeof(dummy)) {
       // There's a reasonable probability this will occur during
       // agent restarts across a large/busy cluster.
       cerr << "Failed to synchronize with agent "
            << "(it's probably exited)" << endl;
       return EXIT_FAILURE;
    }

    close = os::close(pipe[0]);
    if (close.isError()) {
      cerr << "Failed to close pipe[0]: " << close.error() << endl;
      return EXIT_FAILURE;
    }
  }

#ifdef __linux__
  if (flags.unshare_namespace_mnt) {
    if (unshare(CLONE_NEWNS) != 0) {
      cerr << "Failed to unshare mount namespace: "
           << os::strerror(errno) << endl;
      return EXIT_FAILURE;
    }
  }
#endif // __linux__

  // Run additional preparation commands. These are run as the same
  // user and with the environment as the agent.
  if (flags.pre_exec_commands.isSome()) {
    // TODO(jieyu): Use JSON::Array if we have generic parse support.
    JSON::Array array = flags.pre_exec_commands.get();
    foreach (const JSON::Value& value, array.values) {
      if (!value.is<JSON::Object>()) {
        cerr << "Invalid JSON format for flag --commands" << endl;
        return EXIT_FAILURE;
      }

      Try<CommandInfo> parse = ::protobuf::parse<CommandInfo>(value);
      if (parse.isError()) {
        cerr << "Failed to parse a preparation command: "
             << parse.error() << endl;
        return EXIT_FAILURE;
      }

      if (!parse.get().has_value()) {
        cerr << "The 'value' of a preparation command is not specified" << endl;
        return EXIT_FAILURE;
      }

      Try<Subprocess> s = Error("Not launched");

      if (parse->shell()) {
        s = subprocess(parse->value(), Subprocess::PATH("/dev/null"));
      } else {
        // Launch non-shell command as a subprocess to avoid injecting
        // arbitrary shell commands.
        vector<string> args;
        foreach (const string& arg, parse->arguments()) {
          args.push_back(arg);
        }

        s = subprocess(parse->value(), args, Subprocess::PATH("/dev/null"));
      }

      if (s.isError()) {
        cerr << "Failed to create the pre-exec subprocess: "
             << s.error() << endl;
        return EXIT_FAILURE;
      }

      s->status().await();

      Option<int> status = s->status().get();
      if (status.isNone()) {
        cerr << "Failed to reap the pre-exec subprocess "
             << "'" << value << "'" << endl;
        return EXIT_FAILURE;
      } else if (status.get() != 0) {
        cerr << "The pre-exec subprocess '" << value << "' "
             << "failed" << endl;
        return EXIT_FAILURE;
      }
    }
  }

#ifndef __WINDOWS__
  // NOTE: If 'flags.user' is set, we will get the uid, gid, and the
  // supplementary group ids associated with the specified user before
  // changing the filesystem root. This is because after changing the
  // filesystem root, the current process might no longer have access
  // to /etc/passwd and /etc/group on the host.
  Option<uid_t> uid;
  Option<gid_t> gid;
  vector<gid_t> gids;

  // TODO(gilbert): For the case container user exists, support
  // framework/task/default user -> container user mapping once
  // user namespace and container capabilities is available for
  // mesos container.

  if (flags.user.isSome()) {
    Result<uid_t> _uid = os::getuid(flags.user.get());
    if (!_uid.isSome()) {
      cerr << "Failed to get the uid of user '" << flags.user.get() << "': "
           << (_uid.isError() ? _uid.error() : "not found") << endl;
      return EXIT_FAILURE;
    }

    // No need to change user/groups if the specified user is the same
    // as that of the current process.
    if (_uid.get() != os::getuid().get()) {
      Result<gid_t> _gid = os::getgid(flags.user.get());
      if (!_gid.isSome()) {
        cerr << "Failed to get the gid of user '" << flags.user.get() << "': "
             << (_gid.isError() ? _gid.error() : "not found") << endl;
        return EXIT_FAILURE;
      }

      Try<vector<gid_t>> _gids = os::getgrouplist(flags.user.get());
      if (_gids.isError()) {
        cerr << "Failed to get the supplementary gids of user '"
             << flags.user.get() << "': "
             << (_gids.isError() ? _gids.error() : "not found") << endl;
        return EXIT_FAILURE;
      }

      uid = _uid.get();
      gid = _gid.get();
      gids = _gids.get();
    }
  }
#endif // __WINDOWS__

#ifdef __WINDOWS__
  // Not supported on Windows.
  const Option<std::string> rootfs = None();
#else
  const Option<std::string> rootfs = flags.rootfs;
#endif // __WINDOWS__

  // Change root to a new root, if provided.
  if (rootfs.isSome()) {
    cout << "Changing root to " << rootfs.get() << endl;

    // Verify that rootfs is an absolute path.
    Result<string> realpath = os::realpath(rootfs.get());
    if (realpath.isError()) {
      cerr << "Failed to determine if rootfs is an absolute path: "
           << realpath.error() << endl;
      return EXIT_FAILURE;
    } else if (realpath.isNone()) {
      cerr << "Rootfs path does not exist" << endl;
      return EXIT_FAILURE;
    } else if (realpath.get() != rootfs.get()) {
      cerr << "Rootfs path is not an absolute path" << endl;
      return EXIT_FAILURE;
    }

#ifdef __linux__
    Try<Nothing> chroot = fs::chroot::enter(rootfs.get());
#elif defined(__WINDOWS__)
    Try<Nothing> chroot = Error("`chroot` not supported on Windows");
#else // For any other platform we'll just use POSIX chroot.
    Try<Nothing> chroot = os::chroot(rootfs.get());
#endif // __linux__
    if (chroot.isError()) {
      cerr << "Failed to enter chroot '" << rootfs.get()
           << "': " << chroot.error();
      return EXIT_FAILURE;
    }
  }

  // Change user if provided. Note that we do that after executing the
  // preparation commands so that those commands will be run with the
  // same privilege as the mesos-agent.
#ifndef __WINDOWS__
  if (uid.isSome()) {
    Try<Nothing> setgid = os::setgid(gid.get());
    if (setgid.isError()) {
      cerr << "Failed to set gid to " << gid.get()
           << ": " << setgid.error() << endl;
      return EXIT_FAILURE;
    }

    Try<Nothing> setgroups = os::setgroups(gids, uid);
    if (setgroups.isError()) {
      cerr << "Failed to set supplementary gids: "
           << setgroups.error() << endl;
      return EXIT_FAILURE;
    }

    Try<Nothing> setuid = os::setuid(uid.get());
    if (setuid.isError()) {
      cerr << "Failed to set uid to " << uid.get()
           << ": " << setuid.error() << endl;
      return EXIT_FAILURE;
    }
  }
#endif // __WINDOWS__

  if (flags.working_directory.isSome()) {
    Try<Nothing> chdir = os::chdir(flags.working_directory.get());
    if (chdir.isError()) {
      cerr << "Failed to chdir into current working directory "
           << "'" << flags.working_directory.get() << "': "
           << chdir.error() << endl;
      return EXIT_FAILURE;
    }
  }

  // Relay the environment variables.
  // TODO(jieyu): Consider using a clean environment.

  if (command->shell()) {
    // Execute the command using shell.
    os::execlp(os::Shell::name,
               os::Shell::arg0,
               os::Shell::arg1,
               command->value().c_str(),
               (char*) nullptr);
  } else {
    // Use execvp to launch the command.
    execvp(command->value().c_str(),
           os::raw::Argv(command->arguments()));
  }

  // If we get here, the execle call failed.
  cerr << "Failed to execute command: " << os::strerror(errno) << endl;
  UNREACHABLE();
}

} // namespace slave {
} // namespace internal {
} // namespace mesos {
<|endoftext|>


<commit_before>//
// Created by Miguel Rentes on 23/01/2017.
//

#include "STL.h"

int main(void) {
    int T; cin >> T;
    cout << setiosflags(ios::uppercase);
    cout << setw(0xf) << internal;
    while(T--) {
        double A;
        cin >> A;
        double B;
        cin >> B;
        double C;
        cin >> C;
        // pretty printing A
        cout << "0x" << hex << (int) A << endl;
        // pretty printing B
        // width is 15 minus the number of digits of B
        // because 1 character is for the '.', and 2 for the decimal places
        // so the width is 15 - 1 - 2 = 12 minus the number of digits of B
        unsigned int number_of_digits = 0;
        unsigned int number = B;
        do {
            ++number_of_digits;
            number /= 10;
        } while (number);
        cout << fixed << setprecision(2) << setfill('_') << setw(12-number_of_digits);
        if (B > 0)
            cout << "+" << B << endl;
        else cout << "-" << B << endl;
        // pretty printing C
        cout << scientific << setprecision(9) << C << endl;
    }

    return EXIT_SUCCESS;
}

<commit_msg>Adds pretty printing A solution for Print Pretty challenge.<commit_after>//
// Created by Miguel Rentes on 23/01/2017.
//

#include "STL.h"

int main(void) {
    int T; cin >> T;
    cout << setiosflags(ios::uppercase);
    cout << setw(0xf) << internal;
    while(T--) {
        double A;
        cin >> A;
        double B;
        cin >> B;
        double C;
        cin >> C;
        // pretty printing A
        cout << setw(2) << "0x" << setw(13) << left << hex << (int) A << endl;
        // pretty printing B
        // width is 15 minus the number of digits of B
        // because 1 character is for the '.', and 2 for the decimal places
        // so the width is 15 - 1 - 2 = 12 minus the number of digits of B
        unsigned int number_of_digits = 0;
        unsigned int number = B;
        do {
            ++number_of_digits;
            number /= 10;
        } while (number);
        cout << fixed << setprecision(2) << setfill('_') << setw(12-number_of_digits);
        if (B > 0)
            cout << "+" << B << endl;
        else cout << "-" << B << endl;
        // pretty printing C
        cout << scientific << setprecision(9) << C << endl;
    }

    return EXIT_SUCCESS;
}

<|endoftext|>


<commit_before>#pragma once

#include "pugixml.hpp"
#include <blitz/array.h>

#include "core/angular_quadrature.hpp"
#include "core/global_config.hpp"
#include "core/output_interface.hpp"

#include "sn/sn_sweeper_variant.hpp"

#include "correction_data.hpp"
#include "moc_sweeper_2d3d.hpp"
#include "sn_sweeper_cdd.hpp"
#include "sn_sweeper_factory_cdd.hpp"
#include "source_2d3d.hpp"

namespace mocc { namespace cmdo {
    /**
     * This is an implementation of the 2D3D method. Each plane is treated with
     * a 2-D MoC sweeper, which produces the correction factors needed to treat
     * the entire system with a 3-D corrected diamond difference Sn sweeper.
     */
    class PlaneSweeper_2D3D: public TransportSweeper {
    public:
        PlaneSweeper_2D3D( const pugi::xml_node &input, const CoreMesh &mesh );

        void sweep( int group );

        void initialize();

        /**
         * \brief \copybrief TransportSweeper::get_pin_flux_1g()
         */
        void get_pin_flux_1g( int ig, ArrayB1 &flux ) const;

        /**
         * \brief \copybrief TransportSweeper::set_pin_flux_1g()
         *
         * Delegate to the subbordinate \ref sn::SnSweeper and \ref
         * moc::MoCSweeper.  Return the error from the MoC sweeper.
         */
        real_t set_pin_flux_1g( int group, const ArrayB1 &pin_flux ) {
            sn_sweeper_->set_pin_flux_1g( group, pin_flux );
            real_t diff = moc_sweeper_.set_pin_flux_1g( group, pin_flux );

            return diff;
        }

        /**
         * \brief \copybrief HasOutput::output()
         */
        void output( H5Node &file ) const;

        /**
         * \brief \copybrief TransportSweeper::assign_source()
         *
         * Associate the sweeper with a source. This has to do a little extra
         * work, since the Sn sweeper needs its own source.
         */
        virtual void assign_source( Source* source ) {
            assert( source != nullptr );
            source_ = source;
            moc_sweeper_.assign_source( source );
            /// \todo this static_cast is scary. Maybe think about relaxing the
            /// ownership of the source by FSS and allow the TS to figure out
            /// the types more explicitly...
            Source_2D3D *s = static_cast<Source_2D3D*>(source);
            sn_sweeper_->assign_source( s->get_sn_source() );
        }

        /**
         * \breif \copybrief TransportSweeper::create_source()
         *
         * Create a Source_2D3D object instead of the standard Source class.
         */
        UP_Source_t create_source( const pugi::xml_node &input ) const {
            std::cout << "creating 2d3d source" << std::endl;
            return UP_Source_t( new Source_2D3D( moc_sweeper_, *sn_sweeper_ ) );
        }

        SP_XSMeshHomogenized_t get_homogenized_xsmesh() {
            return sn_sweeper_->get_homogenized_xsmesh();
        }

        /**
         * \brief \copybrief TransportSweeper::calc_fission_source()
         *
         * Override the default \ref TransportSweeper implementation to call the
         * method on one of the sub-sweepers.
         */
        void calc_fission_source( real_t k, ArrayB1 &fission_source ) const {
            moc_sweeper_.calc_fission_source( k, fission_source );
            return;
        }

        /**
         * \brief \copybrief TransportSweeper::total_fission()
         *
         * Override the default \ref TransportSweeper implementation to call the
         * method on one of the sub-sweepers. For now, using the MoC
         * implementation, since it's the finer mesh, generally speaking
         */
        real_t total_fission( bool old ) const {
            return moc_sweeper_.total_fission( old );
        }

        /**
         * \brief \copybrief TransportSweeper::store_old_flux()
         *
         * Defer to the MoC and Sn sweepers.
         */
        void store_old_flux() {
            moc_sweeper_.store_old_flux();
            sn_sweeper_->store_old_flux();
            return;
        }

        /**
         * \brief \copybrief TransportSweeper::set_coarse_data()
         *
         * Delegate to subordinate sweepers.
         */
        void set_coarse_data( CoarseData *cd ) {
            coarse_data_ = cd;
            moc_sweeper_.set_coarse_data( cd );
            sn_sweeper_->set_coarse_data( cd );
        }

    private:
        // Parse the various options from the XML
        void parse_options( const pugi::xml_node &input );
        // Calculate transverse leakage based on the state of the coarse_data_
        // and apply to the MoC sweeper's source.
        void add_tl( int group );

        const CoreMesh& mesh_;
        CDDPair_t pair_;
        UP_SnSweeper_t sn_sweeper_;
        std::shared_ptr<CorrectionData> corrections_;
        MoCSweeper_2D3D moc_sweeper_;
        AngularQuadrature ang_quad_;
        ArrayB2 tl_;

        // Sn-MoC residuals by group sweep
        std::vector<VecF> sn_resid_;

        // Options! Buttons and knobs!!!
        bool expose_sn_; // When asking the sweeper for pin flux, which one?
        bool do_snproject_;
        bool do_tl_;
        int n_inactive_moc_;
        int i_outer_;
        int moc_modulo_;
    };
} } // Namespace mocc::cmdo
<commit_msg>Fix typo in docs<commit_after>#pragma once

#include "pugixml.hpp"
#include <blitz/array.h>

#include "core/angular_quadrature.hpp"
#include "core/global_config.hpp"
#include "core/output_interface.hpp"

#include "sn/sn_sweeper_variant.hpp"

#include "correction_data.hpp"
#include "moc_sweeper_2d3d.hpp"
#include "sn_sweeper_cdd.hpp"
#include "sn_sweeper_factory_cdd.hpp"
#include "source_2d3d.hpp"

namespace mocc { namespace cmdo {
    /**
     * This is an implementation of the 2D3D method. Each plane is treated with
     * a 2-D MoC sweeper, which produces the correction factors needed to treat
     * the entire system with a 3-D corrected diamond difference Sn sweeper.
     */
    class PlaneSweeper_2D3D: public TransportSweeper {
    public:
        PlaneSweeper_2D3D( const pugi::xml_node &input, const CoreMesh &mesh );

        void sweep( int group );

        void initialize();

        /**
         * \brief \copybrief TransportSweeper::get_pin_flux_1g()
         */
        void get_pin_flux_1g( int ig, ArrayB1 &flux ) const;

        /**
         * \brief \copybrief TransportSweeper::set_pin_flux_1g()
         *
         * Delegate to the subbordinate \ref sn::SnSweeper and \ref
         * moc::MoCSweeper.  Return the error from the MoC sweeper.
         */
        real_t set_pin_flux_1g( int group, const ArrayB1 &pin_flux ) {
            sn_sweeper_->set_pin_flux_1g( group, pin_flux );
            real_t diff = moc_sweeper_.set_pin_flux_1g( group, pin_flux );

            return diff;
        }

        /**
         * \brief \copybrief HasOutput::output()
         */
        void output( H5Node &file ) const;

        /**
         * \brief \copybrief TransportSweeper::assign_source()
         *
         * Associate the sweeper with a source. This has to do a little extra
         * work, since the Sn sweeper needs its own source.
         */
        virtual void assign_source( Source* source ) {
            assert( source != nullptr );
            source_ = source;
            moc_sweeper_.assign_source( source );
            /// \todo this static_cast is scary. Maybe think about relaxing the
            /// ownership of the source by FSS and allow the TS to figure out
            /// the types more explicitly...
            Source_2D3D *s = static_cast<Source_2D3D*>(source);
            sn_sweeper_->assign_source( s->get_sn_source() );
        }

        /**
         * \brief \copybrief TransportSweeper::create_source()
         *
         * Create a Source_2D3D object instead of the standard Source class.
         */
        UP_Source_t create_source( const pugi::xml_node &input ) const {
            std::cout << "creating 2d3d source" << std::endl;
            return UP_Source_t( new Source_2D3D( moc_sweeper_, *sn_sweeper_ ) );
        }

        SP_XSMeshHomogenized_t get_homogenized_xsmesh() {
            return sn_sweeper_->get_homogenized_xsmesh();
        }

        /**
         * \brief \copybrief TransportSweeper::calc_fission_source()
         *
         * Override the default \ref TransportSweeper implementation to call the
         * method on one of the sub-sweepers.
         */
        void calc_fission_source( real_t k, ArrayB1 &fission_source ) const {
            moc_sweeper_.calc_fission_source( k, fission_source );
            return;
        }

        /**
         * \brief \copybrief TransportSweeper::total_fission()
         *
         * Override the default \ref TransportSweeper implementation to call the
         * method on one of the sub-sweepers. For now, using the MoC
         * implementation, since it's the finer mesh, generally speaking
         */
        real_t total_fission( bool old ) const {
            return moc_sweeper_.total_fission( old );
        }

        /**
         * \brief \copybrief TransportSweeper::store_old_flux()
         *
         * Defer to the MoC and Sn sweepers.
         */
        void store_old_flux() {
            moc_sweeper_.store_old_flux();
            sn_sweeper_->store_old_flux();
            return;
        }

        /**
         * \brief \copybrief TransportSweeper::set_coarse_data()
         *
         * Delegate to subordinate sweepers.
         */
        void set_coarse_data( CoarseData *cd ) {
            coarse_data_ = cd;
            moc_sweeper_.set_coarse_data( cd );
            sn_sweeper_->set_coarse_data( cd );
        }

    private:
        // Parse the various options from the XML
        void parse_options( const pugi::xml_node &input );
        // Calculate transverse leakage based on the state of the coarse_data_
        // and apply to the MoC sweeper's source.
        void add_tl( int group );

        const CoreMesh& mesh_;
        CDDPair_t pair_;
        UP_SnSweeper_t sn_sweeper_;
        std::shared_ptr<CorrectionData> corrections_;
        MoCSweeper_2D3D moc_sweeper_;
        AngularQuadrature ang_quad_;
        ArrayB2 tl_;

        // Sn-MoC residuals by group sweep
        std::vector<VecF> sn_resid_;

        // Options! Buttons and knobs!!!
        bool expose_sn_; // When asking the sweeper for pin flux, which one?
        bool do_snproject_;
        bool do_tl_;
        int n_inactive_moc_;
        int i_outer_;
        int moc_modulo_;
    };
} } // Namespace mocc::cmdo
<|endoftext|>


<commit_before>/****************************************************************************
 *
 *   Copyright (c) 2016 PX4 Development Team. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/**
 * @file motor_ramp.cpp
 * Application to test motor ramp up.
 *
 * @author Andreas Antener <andreas@uaventure.com>
 * @author Roman Bapst <bapstroman@gmail.com>
 */

#include <px4_config.h>
#include <px4_defines.h>
#include <px4_tasks.h>
#include <px4_posix.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <poll.h>

#include <arch/board/board.h>
#include <drivers/drv_hrt.h>
#include <drivers/drv_pwm_output.h>
#include <platforms/px4_defines.h>

#include "systemlib/systemlib.h"
#include "systemlib/err.h"

enum RampState {
	RAMP_INIT,
	RAMP_MIN,
	RAMP_RAMP,
	RAMP_WAIT
};

enum Mode {
	RAMP,
	SINE,
	SQUARE
};

static bool _thread_should_exit = false;		/**< motor_ramp exit flag */
static bool _thread_running = false;		/**< motor_ramp status flag */
static int _motor_ramp_task;				/**< Handle of motor_ramp task / thread */
static float _ramp_time;
static int _min_pwm;
static int _max_pwm;
static Mode _mode;
static char *_mode_c;

/**
 * motor_ramp management function.
 */
extern "C" __EXPORT int motor_ramp_main(int argc, char *argv[]);

/**
 * Mainloop of motor_ramp.
 */
int motor_ramp_thread_main(int argc, char *argv[]);

bool min_pwm_valid(unsigned pwm_value);

bool max_pwm_valid(unsigned pwm_value);

int set_min_pwm(int fd, unsigned long max_channels, unsigned pwm_value);

int set_out(int fd, unsigned long max_channels, float output);

int prepare(int fd, unsigned long *max_channels);

/**
 * Print the correct usage.
 */
static void usage(const char *reason);

static void
usage(const char *reason)
{
	if (reason) {
		PX4_ERR("%s", reason);
	}

	PX4_WARN("\n\nWARNING: motors will ramp up to full speed!\n\n"
		 "Usage: motor_ramp <mode> <min_pwm> <time> [<max_pwm>]\n"
		 "<mode> can be one of (ramp|sine|square)\n\n"
		 "Example:\n"
		 "sdlog2 on\n"
		 "mc_att_control stop\n"
		 "fw_att_control stop\n"
		 "motor_ramp sine 1100 0.5\n");
}

/**
 * The motor_ramp app only briefly exists to start
 * the background job. The stack size assigned in the
 * Makefile does only apply to this management task.
 *
 * The actual stack size should be set in the call
 * to task_create().
 */
int motor_ramp_main(int argc, char *argv[])
{
	if (argc < 4) {
		usage("missing parameters");
		return 1;
	}

	if (_thread_running) {
		PX4_WARN("motor_ramp already running\n");
		/* this is not an error */
		return 0;
	}

	if (!strcmp(argv[1], "ramp")) {
		_mode = RAMP;

	} else if (!strcmp(argv[1], "sine")) {
		_mode = SINE;

	} else if (!strcmp(argv[1], "square")) {
		_mode = SQUARE;

	} else {
		usage("selected mode not valid");
		return 1;
	}

	_mode_c = argv[1];

	_min_pwm = atoi(argv[2]);

	if (!min_pwm_valid(_min_pwm)) {
		usage("min PWM not in range");
		return 1;
	}

	_ramp_time = atof(argv[3]);

	if (argc > 4) {
		_max_pwm = atoi(argv[4]);

		if (!max_pwm_valid(_max_pwm)) {
			usage("max PWM not in range");
			return 1;
		}

	} else {
		_max_pwm = 2000;
	}

	if (!(_ramp_time > 0)) {
		usage("ramp time must be greater than 0");
		return 1;
	}

	_thread_should_exit = false;
	_motor_ramp_task = px4_task_spawn_cmd("motor_ramp",
					      SCHED_DEFAULT,
					      SCHED_PRIORITY_DEFAULT + 40,
					      2000,
					      motor_ramp_thread_main,
					      (argv) ? (char *const *)&argv[2] : (char *const *)nullptr);
	return 0;

	usage("unrecognized command");
	return 1;
}

bool min_pwm_valid(unsigned pwm_value)
{
	return pwm_value >= 900 && pwm_value <= 1500;
}

bool max_pwm_valid(unsigned pwm_value)
{
	return pwm_value <= 2100 && pwm_value > _min_pwm;
}

int set_min_pwm(int fd, unsigned long max_channels, unsigned pwm_value)
{
	int ret;

	struct pwm_output_values pwm_values;
	memset(&pwm_values, 0, sizeof(pwm_values));

	pwm_values.channel_count = max_channels;

	for (int i = 0; i < max_channels; i++) {
		pwm_values.values[i] = pwm_value;
	}

	ret = px4_ioctl(fd, PWM_SERVO_SET_MIN_PWM, (long unsigned int)&pwm_values);

	if (ret != OK) {
		PX4_ERR("failed setting min values");
		return 1;
	}

	return 0;
}

int set_out(int fd, unsigned long max_channels, float output)
{
	int ret;
	int pwm = (_max_pwm - _min_pwm) * output + _min_pwm;

	for (unsigned i = 0; i < max_channels; i++) {

		ret = ioctl(fd, PWM_SERVO_SET(i), pwm);

		if (ret != OK) {
			PX4_ERR("PWM_SERVO_SET(%d), value: %d", i, pwm);
			return 1;
		}
	}

	return 0;
}

int prepare(int fd, unsigned long *max_channels)
{
	/* make sure no other source is publishing control values now */
	struct actuator_controls_s actuators;
	int act_sub = orb_subscribe(ORB_ID_VEHICLE_ATTITUDE_CONTROLS);

	/* clear changed flag */
	orb_copy(ORB_ID_VEHICLE_ATTITUDE_CONTROLS, act_sub, &actuators);

	/* wait 50 ms */
	usleep(50000);

	/* now expect nothing changed on that topic */
	bool orb_updated;
	orb_check(act_sub, &orb_updated);

	if (orb_updated) {
		PX4_ERR("ABORTING! Attitude control still active. Please ensure to shut down all controllers:\n"
			"\tmc_att_control stop\n"
			"\tfw_att_control stop\n");
		return 1;
	}

	/* get number of channels available on the device */
	if (px4_ioctl(fd, PWM_SERVO_GET_COUNT, (unsigned long)max_channels) != OK) {
		PX4_ERR("PWM_SERVO_GET_COUNT");
		return 1;
	}

	/* tell IO/FMU that its ok to disable its safety with the switch */
	if (px4_ioctl(fd, PWM_SERVO_SET_ARM_OK, 0) != OK) {
		PX4_ERR("PWM_SERVO_SET_ARM_OK");
		return 1;
	}

	/* tell IO/FMU that the system is armed (it will output values if safety is off) */
	if (px4_ioctl(fd, PWM_SERVO_ARM, 0) != OK) {
		PX4_ERR("PWM_SERVO_ARM");
		return 1;
	}

	/* tell IO to switch off safety without using the safety switch */
	if (px4_ioctl(fd, PWM_SERVO_SET_FORCE_SAFETY_OFF, 0) != OK) {
		PX4_ERR("PWM_SERVO_SET_FORCE_SAFETY_OFF");
		return 1;
	}

	return 0;
}

int motor_ramp_thread_main(int argc, char *argv[])
{
	_thread_running = true;

	char *dev = PWM_OUTPUT0_DEVICE_PATH;
	unsigned long max_channels = 0;

	int fd = px4_open(dev, 0);

	if (fd < 0) {
		PX4_ERR("can't open %s", dev);
	}

	if (prepare(fd, &max_channels) != OK) {
		_thread_should_exit = true;
	}

	set_out(fd, max_channels, 0.0f);

	float dt = 0.001f; // prevent division with 0
	float timer = 0.0f;
	hrt_abstime start = 0;
	hrt_abstime last_run = 0;

	enum RampState ramp_state = RAMP_INIT;
	float output = 0.0f;

	while (!_thread_should_exit) {

		if (last_run > 0) {
			dt = hrt_elapsed_time(&last_run) * 1e-6;

		} else {
			start = hrt_absolute_time();
		}

		last_run = hrt_absolute_time();
		timer = hrt_elapsed_time(&start) * 1e-6;

		switch (ramp_state) {
		case RAMP_INIT: {
				PX4_WARN("setting pwm min: %d", _min_pwm);
				set_min_pwm(fd, max_channels, _min_pwm);
				ramp_state = RAMP_MIN;
				break;
			}

		case RAMP_MIN: {
				if (timer > 3.0f) {
					PX4_WARN("starting %s: %.2f sec", _mode_c, (double)_ramp_time);
					start = hrt_absolute_time();
					ramp_state = RAMP_RAMP;
				}

				set_out(fd, max_channels, output);
				break;
			}

		case RAMP_RAMP: {
				if (_mode == RAMP) {
					output += 1000.0f * dt / (_max_pwm - _min_pwm) / _ramp_time;

				} else if (_mode == SINE) {
					// sine outpout with period T = _ramp_time and magnitude between [0,1]
					// phase shift makes sure that it starts at zero when timer is zero
					output = 0.5f * (1.0f + sinf(M_TWOPI_F * timer / _ramp_time - M_PI_2_F));

				} else if (_mode == SQUARE) {
					output = fmodf(timer, _ramp_time) > (_ramp_time * 0.5f) ? 1.0f : 0.0f;
				}

				if ((output > 1.0f && _mode == RAMP) || (timer > 3.0f * _ramp_time)) {
					// for ramp mode we set output to 1, for others we just leave it as is
					output = _mode != RAMP ? output : 1.0f;
					start = hrt_absolute_time();
					ramp_state = RAMP_WAIT;
					PX4_WARN("%s finished, waiting", _mode_c);
				}

				set_out(fd, max_channels, output);
				break;
			}

		case RAMP_WAIT: {
				if (timer > 0.5f) {
					_thread_should_exit = true;
					PX4_WARN("stopping");
					break;
				}

				set_out(fd, max_channels, output);
				break;
			}
		}

		// rate limit
		usleep(2000);
	}

	if (fd >= 0) {
		/* disarm */
		ioctl(fd, PWM_SERVO_DISARM, 0);

		px4_close(fd);
	}

	_thread_running = false;

	return 0;
}
<commit_msg>motor_ramp: add documentation<commit_after>/****************************************************************************
 *
 *   Copyright (c) 2016 PX4 Development Team. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/**
 * @file motor_ramp.cpp
 *
 * @author Andreas Antener <andreas@uaventure.com>
 * @author Roman Bapst <bapstroman@gmail.com>
 */

#include <px4_config.h>
#include <px4_defines.h>
#include <px4_module.h>
#include <px4_tasks.h>
#include <px4_posix.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <poll.h>

#include <arch/board/board.h>
#include <drivers/drv_hrt.h>
#include <drivers/drv_pwm_output.h>
#include <platforms/px4_defines.h>

#include "systemlib/systemlib.h"
#include "systemlib/err.h"

enum RampState {
	RAMP_INIT,
	RAMP_MIN,
	RAMP_RAMP,
	RAMP_WAIT
};

enum Mode {
	RAMP,
	SINE,
	SQUARE
};

static bool _thread_should_exit = false;		/**< motor_ramp exit flag */
static bool _thread_running = false;		/**< motor_ramp status flag */
static int _motor_ramp_task;				/**< Handle of motor_ramp task / thread */
static float _ramp_time;
static int _min_pwm;
static int _max_pwm;
static Mode _mode;
static char *_mode_c;

/**
 * motor_ramp management function.
 */
extern "C" __EXPORT int motor_ramp_main(int argc, char *argv[]);

/**
 * Mainloop of motor_ramp.
 */
int motor_ramp_thread_main(int argc, char *argv[]);

bool min_pwm_valid(unsigned pwm_value);

bool max_pwm_valid(unsigned pwm_value);

int set_min_pwm(int fd, unsigned long max_channels, unsigned pwm_value);

int set_out(int fd, unsigned long max_channels, float output);

int prepare(int fd, unsigned long *max_channels);

/**
 * Print the correct usage.
 */
static void usage(const char *reason);

static void
usage(const char *reason)
{
	if (reason) {
		PX4_ERR("%s", reason);
	}

	PRINT_MODULE_DESCRIPTION(
		R"DESCR_STR(
### Description
Application to test motor ramp up.

Before starting, make sure to stop any running attitude controller:
$ mc_att_control stop
$ fw_att_control stop

When starting, a background task is started, runs for several seconds (as specified), then exits.

Note: this command currently only supports the `/dev/pwm_output0` output.

### Example
$ motor_ramp sine 1100 0.5
)DESCR_STR");

	PRINT_MODULE_USAGE_NAME_SIMPLE("motor_ramp", "command");
	PRINT_MODULE_USAGE_ARG("ramp|sine|square", "mode", false);
	PRINT_MODULE_USAGE_ARG("<min_pwm> <time> [<max_pwm>]", "pwm value in us, time in sec", false);

	PRINT_MODULE_USAGE_PARAM_COMMENT("WARNING: motors will ramp up to full speed!");

}

/**
 * The motor_ramp app only briefly exists to start
 * the background job. The stack size assigned in the
 * Makefile does only apply to this management task.
 *
 * The actual stack size should be set in the call
 * to task_create().
 */
int motor_ramp_main(int argc, char *argv[])
{
	if (argc < 4) {
		usage("missing parameters");
		return 1;
	}

	if (_thread_running) {
		PX4_WARN("motor_ramp already running\n");
		/* this is not an error */
		return 0;
	}

	if (!strcmp(argv[1], "ramp")) {
		_mode = RAMP;

	} else if (!strcmp(argv[1], "sine")) {
		_mode = SINE;

	} else if (!strcmp(argv[1], "square")) {
		_mode = SQUARE;

	} else {
		usage("selected mode not valid");
		return 1;
	}

	_mode_c = argv[1];

	_min_pwm = atoi(argv[2]);

	if (!min_pwm_valid(_min_pwm)) {
		usage("min PWM not in range");
		return 1;
	}

	_ramp_time = atof(argv[3]);

	if (argc > 4) {
		_max_pwm = atoi(argv[4]);

		if (!max_pwm_valid(_max_pwm)) {
			usage("max PWM not in range");
			return 1;
		}

	} else {
		_max_pwm = 2000;
	}

	if (!(_ramp_time > 0)) {
		usage("ramp time must be greater than 0");
		return 1;
	}

	_thread_should_exit = false;
	_motor_ramp_task = px4_task_spawn_cmd("motor_ramp",
					      SCHED_DEFAULT,
					      SCHED_PRIORITY_DEFAULT + 40,
					      2000,
					      motor_ramp_thread_main,
					      (argv) ? (char *const *)&argv[2] : (char *const *)nullptr);
	return 0;

	usage("unrecognized command");
	return 1;
}

bool min_pwm_valid(unsigned pwm_value)
{
	return pwm_value >= 900 && pwm_value <= 1500;
}

bool max_pwm_valid(unsigned pwm_value)
{
	return pwm_value <= 2100 && pwm_value > _min_pwm;
}

int set_min_pwm(int fd, unsigned long max_channels, unsigned pwm_value)
{
	int ret;

	struct pwm_output_values pwm_values;
	memset(&pwm_values, 0, sizeof(pwm_values));

	pwm_values.channel_count = max_channels;

	for (int i = 0; i < max_channels; i++) {
		pwm_values.values[i] = pwm_value;
	}

	ret = px4_ioctl(fd, PWM_SERVO_SET_MIN_PWM, (long unsigned int)&pwm_values);

	if (ret != OK) {
		PX4_ERR("failed setting min values");
		return 1;
	}

	return 0;
}

int set_out(int fd, unsigned long max_channels, float output)
{
	int ret;
	int pwm = (_max_pwm - _min_pwm) * output + _min_pwm;

	for (unsigned i = 0; i < max_channels; i++) {

		ret = ioctl(fd, PWM_SERVO_SET(i), pwm);

		if (ret != OK) {
			PX4_ERR("PWM_SERVO_SET(%d), value: %d", i, pwm);
			return 1;
		}
	}

	return 0;
}

int prepare(int fd, unsigned long *max_channels)
{
	/* make sure no other source is publishing control values now */
	struct actuator_controls_s actuators;
	int act_sub = orb_subscribe(ORB_ID_VEHICLE_ATTITUDE_CONTROLS);

	/* clear changed flag */
	orb_copy(ORB_ID_VEHICLE_ATTITUDE_CONTROLS, act_sub, &actuators);

	/* wait 50 ms */
	usleep(50000);

	/* now expect nothing changed on that topic */
	bool orb_updated;
	orb_check(act_sub, &orb_updated);

	if (orb_updated) {
		PX4_ERR("ABORTING! Attitude control still active. Please ensure to shut down all controllers:\n"
			"\tmc_att_control stop\n"
			"\tfw_att_control stop\n");
		return 1;
	}

	/* get number of channels available on the device */
	if (px4_ioctl(fd, PWM_SERVO_GET_COUNT, (unsigned long)max_channels) != OK) {
		PX4_ERR("PWM_SERVO_GET_COUNT");
		return 1;
	}

	/* tell IO/FMU that its ok to disable its safety with the switch */
	if (px4_ioctl(fd, PWM_SERVO_SET_ARM_OK, 0) != OK) {
		PX4_ERR("PWM_SERVO_SET_ARM_OK");
		return 1;
	}

	/* tell IO/FMU that the system is armed (it will output values if safety is off) */
	if (px4_ioctl(fd, PWM_SERVO_ARM, 0) != OK) {
		PX4_ERR("PWM_SERVO_ARM");
		return 1;
	}

	/* tell IO to switch off safety without using the safety switch */
	if (px4_ioctl(fd, PWM_SERVO_SET_FORCE_SAFETY_OFF, 0) != OK) {
		PX4_ERR("PWM_SERVO_SET_FORCE_SAFETY_OFF");
		return 1;
	}

	return 0;
}

int motor_ramp_thread_main(int argc, char *argv[])
{
	_thread_running = true;

	char *dev = PWM_OUTPUT0_DEVICE_PATH;
	unsigned long max_channels = 0;

	int fd = px4_open(dev, 0);

	if (fd < 0) {
		PX4_ERR("can't open %s", dev);
	}

	if (prepare(fd, &max_channels) != OK) {
		_thread_should_exit = true;
	}

	set_out(fd, max_channels, 0.0f);

	float dt = 0.001f; // prevent division with 0
	float timer = 0.0f;
	hrt_abstime start = 0;
	hrt_abstime last_run = 0;

	enum RampState ramp_state = RAMP_INIT;
	float output = 0.0f;

	while (!_thread_should_exit) {

		if (last_run > 0) {
			dt = hrt_elapsed_time(&last_run) * 1e-6;

		} else {
			start = hrt_absolute_time();
		}

		last_run = hrt_absolute_time();
		timer = hrt_elapsed_time(&start) * 1e-6;

		switch (ramp_state) {
		case RAMP_INIT: {
				PX4_INFO("setting pwm min: %d", _min_pwm);
				set_min_pwm(fd, max_channels, _min_pwm);
				ramp_state = RAMP_MIN;
				break;
			}

		case RAMP_MIN: {
				if (timer > 3.0f) {
					PX4_INFO("starting %s: %.2f sec", _mode_c, (double)_ramp_time);
					start = hrt_absolute_time();
					ramp_state = RAMP_RAMP;
				}

				set_out(fd, max_channels, output);
				break;
			}

		case RAMP_RAMP: {
				if (_mode == RAMP) {
					output += 1000.0f * dt / (_max_pwm - _min_pwm) / _ramp_time;

				} else if (_mode == SINE) {
					// sine outpout with period T = _ramp_time and magnitude between [0,1]
					// phase shift makes sure that it starts at zero when timer is zero
					output = 0.5f * (1.0f + sinf(M_TWOPI_F * timer / _ramp_time - M_PI_2_F));

				} else if (_mode == SQUARE) {
					output = fmodf(timer, _ramp_time) > (_ramp_time * 0.5f) ? 1.0f : 0.0f;
				}

				if ((output > 1.0f && _mode == RAMP) || (timer > 3.0f * _ramp_time)) {
					// for ramp mode we set output to 1, for others we just leave it as is
					output = _mode != RAMP ? output : 1.0f;
					start = hrt_absolute_time();
					ramp_state = RAMP_WAIT;
					PX4_INFO("%s finished, waiting", _mode_c);
				}

				set_out(fd, max_channels, output);
				break;
			}

		case RAMP_WAIT: {
				if (timer > 0.5f) {
					_thread_should_exit = true;
					PX4_INFO("stopping");
					break;
				}

				set_out(fd, max_channels, output);
				break;
			}
		}

		// rate limit
		usleep(2000);
	}

	if (fd >= 0) {
		/* disarm */
		ioctl(fd, PWM_SERVO_DISARM, 0);

		px4_close(fd);
	}

	_thread_running = false;

	return 0;
}
<|endoftext|>