<commit_before>/*
 * SessionSVN.cpp
 *
 * Copyright (C) 2009-11 by RStudio, Inc.
 *
 * This program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionSVN.hpp"

#include <boost/bind.hpp>

#include <core/rapidxml/rapidxml.hpp>
#include <core/system/Environment.hpp>
#include <core/system/Process.hpp>
#include <core/system/ShellUtils.hpp>
#include <core/Exec.hpp>

#include <session/projects/SessionProjects.hpp>
#include <session/SessionModuleContext.hpp>
#include <session/SessionOptions.hpp>

using namespace core;
using namespace core::shell_utils;

namespace session {
namespace modules {
namespace svn {

namespace {

/** GLOBAL STATE **/
FilePath s_workingDir;

core::system::ProcessOptions procOptions()
{
   core::system::ProcessOptions options;

   // detach the session so there is no terminal
#ifndef _WIN32
   options.detachSession = true;
#endif

   // get current environment for modification prior to passing to child
   core::system::Options childEnv;
   core::system::environment(&childEnv);

   // TODO: Add Subversion bin dir to path if necessary

   // add postback directory to PATH
   FilePath postbackDir = session::options().rpostbackPath().parent();
   core::system::addToPath(&childEnv, postbackDir.absolutePath());

   if (!s_workingDir.empty())
      options.workingDir = s_workingDir;
   else
      options.workingDir = projects::projectContext().directory();

   // on windows set HOME to USERPROFILE
#ifdef _WIN32
   std::string userProfile = core::system::getenv(childEnv, "USERPROFILE");
   core::system::setenv(&childEnv, "HOME", userProfile);
#endif

   // set custom environment
   options.environment = childEnv;

   return options;
}

Error runSvn(const ShellArgs& args,
             std::string* pStdOut=NULL,
             std::string* pStdErr=NULL,
             int* pExitCode=NULL)
{
   core::system::ProcessOptions options = procOptions();
#ifdef _WIN32
   options.detachProcess = true;
#endif

   core::system::ProcessResult result;

#ifdef _WIN32
   Error error = core::system::runProgram("svn.exe",
                                          args.args(),
                                          std::string(),
                                          options,
                                          &result);
#else
   Error error = core::system::runCommand(ShellCommand("svn") << args.args(),
                                          options,
                                          &result);
#endif
   if (error)
      return error;

   if (pStdOut)
      *pStdOut = result.stdOut;
   if (pStdErr)
      *pStdErr = result.stdErr;
   if (pExitCode)
      *pExitCode = result.exitStatus;

   if (result.exitStatus != EXIT_SUCCESS)
   {
      LOG_DEBUG_MESSAGE(result.stdErr);
   }

   return Success();
}

} // namespace

bool isSvnInstalled()
{
   int exitCode;
   Error error = runSvn(ShellArgs() << "help", NULL, NULL, &exitCode);

   if (error)
   {
      LOG_ERROR(error);
      return false;
   }

   return exitCode == EXIT_SUCCESS;
}

bool isSvnDirectory(const core::FilePath& workingDir)
{
   if (workingDir.empty())
      return false;

   core::system::ProcessOptions options = procOptions();
   options.workingDir = workingDir;

   core::system::ProcessResult result;

   Error error = core::system::runCommand("svn info",
                                          "",
                                          options,
                                          &result);

   if (error)
      return false;

   return result.exitStatus == EXIT_SUCCESS;
}

bool isSvnEnabled()
{
   return !s_workingDir.empty();
}

std::string translateItemStatus(const std::string& status)
{
   if (status == "added")
      return "A";
   if (status == "conflicted")
      return "C";
   if (status == "deleted")
      return "D";
   if (status == "external")
      return "X";
   if (status == "ignored")
      return "I";
   if (status == "incomplete")
      return "!";
   if (status == "merged")      // ??
      return "G";
   if (status == "missing")
      return "!";
   if (status == "modified")
      return "M";
   if (status == "none")
      return " ";
   if (status == "normal")      // ??
      return " ";
   if (status == "obstructed")
      return "~";
   if (status == "replaced")
      return "~";
   if (status == "unversioned")
      return "?";

   return " ";
}

#define FOREACH_NODE(parent, varname, name) \
   for (rapidxml::xml_node<>* varname = parent->first_node(name); \
        varname; \
        varname = varname->next_sibling(name))

std::string attr_value(rapidxml::xml_node<>* pNode, const std::string& attrName)
{
   if (!pNode)
      return std::string();
   rapidxml::xml_attribute<>* pAttr = pNode->first_attribute(attrName.c_str());
   if (!pAttr)
      return std::string();
   return std::string(pAttr->value());
}

FilePath resolveAliasedJsonPath(const json::Value& value)
{
   return module_context::resolveAliasedPath(value.get_str());
}

Error svnAdd(const json::JsonRpcRequest& request,
             json::JsonRpcResponse* pResponse)
{
   json::Array files;
   Error error = json::readParams(request.params, &files);
   if (error)
      return error;

   std::vector<FilePath> paths;
   std::transform(files.begin(), files.end(), std::back_inserter(paths),
                  &resolveAliasedJsonPath);

   error = runSvn(ShellArgs() << "add" << "--" << paths);
   if (error)
      return error;

   return Success();
}

Error svnDelete(const json::JsonRpcRequest& request,
                json::JsonRpcResponse* pResponse)
{
   json::Array files;
   Error error = json::readParams(request.params, &files);
   if (error)
      return error;

   std::vector<FilePath> paths;
   std::transform(files.begin(), files.end(), std::back_inserter(paths),
                  &resolveAliasedJsonPath);

   error = runSvn(ShellArgs() << "delete" << "--" << paths);
   if (error)
      return error;

   return Success();
}

Error svnRevert(const json::JsonRpcRequest& request,
                json::JsonRpcResponse* pResponse)
{
   json::Array files;
   Error error = json::readParams(request.params, &files);
   if (error)
      return error;

   std::vector<FilePath> paths;
   std::transform(files.begin(), files.end(), std::back_inserter(paths),
                  &resolveAliasedJsonPath);

   error = runSvn(ShellArgs() << "revert" << "--" << paths);
   if (error)
      return error;

   return Success();
}

Error svnStatus(const json::JsonRpcRequest& request,
                json::JsonRpcResponse* pResponse)
{
   std::string stdOut, stdErr;
   int exitCode;
   Error error = runSvn(
         ShellArgs() << "status" << "--xml" << "--ignore-externals",
         &stdOut,
         &stdErr,
         &exitCode);
   if (error)
      return error;

   if (exitCode != EXIT_SUCCESS)
   {
      LOG_ERROR_MESSAGE(stdErr);
      return Success();
   }

   std::vector<char> xmlData;
   xmlData.reserve(stdOut.size() + 1);
   std::copy(stdOut.begin(),
             stdOut.end(),
             std::back_inserter(xmlData));
   xmlData.push_back('\0'); // null terminator

   using namespace rapidxml;
   xml_document<> doc;
   doc.parse<0>(&(xmlData[0]));

   json::Array results;

   xml_node<>* pStatus = doc.first_node("status");
   if (pStatus)
   {
      FOREACH_NODE(pStatus, pList,)
      {
         FOREACH_NODE(pList, pEntry, "entry")
         {
            std::string path = attr_value(pEntry, "path");
            if (path.empty())
            {
               LOG_ERROR_MESSAGE("Path attribute not found");
               continue;
            }

            xml_node<>* pStatus = pEntry->first_node("wc-status");
            if (!pStatus)
            {
               LOG_ERROR_MESSAGE("Status node not found");
               continue;
            }

            std::string item = attr_value(pStatus, "item");
            if (item.empty())
            {
               LOG_ERROR_MESSAGE("Item attribute not found");
               continue;
            }

            json::Object info;
            info["status"] = translateItemStatus(item);
            // TODO: escape path relative to <target>
            info["path"] = path;
            info["raw_path"] = module_context::createAliasedPath(
                  projects::projectContext().directory().childPath(path));
            results.push_back(info);
         }
      }
   }

   pResponse->setResult(results);

   return Success();
}

Error initialize()
{
   // install rpc methods
   using boost::bind;
   using namespace module_context;
   ExecBlock initBlock ;
   initBlock.addFunctions()
      (bind(registerRpcMethod, "svn_add", svnAdd))
      (bind(registerRpcMethod, "svn_delete", svnDelete))
      (bind(registerRpcMethod, "svn_revert", svnRevert))
      (bind(registerRpcMethod, "svn_status", svnStatus));
   Error error = initBlock.execute();
   if (error)
      return error;

   return Success();
}

Error initializeSvn(const core::FilePath& workingDir)
{
   s_workingDir = workingDir;
   return Success();
}

} // namespace svn
} // namespace modules
} //namespace session
<commit_msg>Show SVN properties status if applicable<commit_after>/*
 * SessionSVN.cpp
 *
 * Copyright (C) 2009-11 by RStudio, Inc.
 *
 * This program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionSVN.hpp"

#include <boost/bind.hpp>

#include <core/rapidxml/rapidxml.hpp>
#include <core/system/Environment.hpp>
#include <core/system/Process.hpp>
#include <core/system/ShellUtils.hpp>
#include <core/Exec.hpp>

#include <session/projects/SessionProjects.hpp>
#include <session/SessionModuleContext.hpp>
#include <session/SessionOptions.hpp>

using namespace core;
using namespace core::shell_utils;

namespace session {
namespace modules {
namespace svn {

namespace {

/** GLOBAL STATE **/
FilePath s_workingDir;

core::system::ProcessOptions procOptions()
{
   core::system::ProcessOptions options;

   // detach the session so there is no terminal
#ifndef _WIN32
   options.detachSession = true;
#endif

   // get current environment for modification prior to passing to child
   core::system::Options childEnv;
   core::system::environment(&childEnv);

   // TODO: Add Subversion bin dir to path if necessary

   // add postback directory to PATH
   FilePath postbackDir = session::options().rpostbackPath().parent();
   core::system::addToPath(&childEnv, postbackDir.absolutePath());

   if (!s_workingDir.empty())
      options.workingDir = s_workingDir;
   else
      options.workingDir = projects::projectContext().directory();

   // on windows set HOME to USERPROFILE
#ifdef _WIN32
   std::string userProfile = core::system::getenv(childEnv, "USERPROFILE");
   core::system::setenv(&childEnv, "HOME", userProfile);
#endif

   // set custom environment
   options.environment = childEnv;

   return options;
}

Error runSvn(const ShellArgs& args,
             std::string* pStdOut=NULL,
             std::string* pStdErr=NULL,
             int* pExitCode=NULL)
{
   core::system::ProcessOptions options = procOptions();
#ifdef _WIN32
   options.detachProcess = true;
#endif

   core::system::ProcessResult result;

#ifdef _WIN32
   Error error = core::system::runProgram("svn.exe",
                                          args.args(),
                                          std::string(),
                                          options,
                                          &result);
#else
   Error error = core::system::runCommand(ShellCommand("svn") << args.args(),
                                          options,
                                          &result);
#endif
   if (error)
      return error;

   if (pStdOut)
      *pStdOut = result.stdOut;
   if (pStdErr)
      *pStdErr = result.stdErr;
   if (pExitCode)
      *pExitCode = result.exitStatus;

   if (result.exitStatus != EXIT_SUCCESS)
   {
      LOG_DEBUG_MESSAGE(result.stdErr);
   }

   return Success();
}

} // namespace

bool isSvnInstalled()
{
   int exitCode;
   Error error = runSvn(ShellArgs() << "help", NULL, NULL, &exitCode);

   if (error)
   {
      LOG_ERROR(error);
      return false;
   }

   return exitCode == EXIT_SUCCESS;
}

bool isSvnDirectory(const core::FilePath& workingDir)
{
   if (workingDir.empty())
      return false;

   core::system::ProcessOptions options = procOptions();
   options.workingDir = workingDir;

   core::system::ProcessResult result;

   Error error = core::system::runCommand("svn info",
                                          "",
                                          options,
                                          &result);

   if (error)
      return false;

   return result.exitStatus == EXIT_SUCCESS;
}

bool isSvnEnabled()
{
   return !s_workingDir.empty();
}

std::string translateItemStatus(const std::string& status)
{
   if (status == "added")
      return "A";
   if (status == "conflicted")
      return "C";
   if (status == "deleted")
      return "D";
   if (status == "external")
      return "X";
   if (status == "ignored")
      return "I";
   if (status == "incomplete")
      return "!";
   if (status == "merged")      // ??
      return "G";
   if (status == "missing")
      return "!";
   if (status == "modified")
      return "M";
   if (status == "none")
      return " ";
   if (status == "normal")      // ??
      return " ";
   if (status == "obstructed")
      return "~";
   if (status == "replaced")
      return "~";
   if (status == "unversioned")
      return "?";

   return " ";
}

int rankItemStatus(const std::string& status)
{
   if (status == " " || status.empty())
      return 10;

   if (status == "I")
      return 7;

   if (status == "M")
      return 1;

   if (status == "C")
      return 0;

   return 5;
}

std::string topStatus(const std::string& a, const std::string& b)
{
   if (rankItemStatus(a) <= rankItemStatus(b))
      return a;
   return b;
}

#define FOREACH_NODE(parent, varname, name) \
   for (rapidxml::xml_node<>* varname = parent->first_node(name); \
        varname; \
        varname = varname->next_sibling(name))

std::string attr_value(rapidxml::xml_node<>* pNode, const std::string& attrName)
{
   if (!pNode)
      return std::string();
   rapidxml::xml_attribute<>* pAttr = pNode->first_attribute(attrName.c_str());
   if (!pAttr)
      return std::string();
   return std::string(pAttr->value());
}

FilePath resolveAliasedJsonPath(const json::Value& value)
{
   return module_context::resolveAliasedPath(value.get_str());
}

Error svnAdd(const json::JsonRpcRequest& request,
             json::JsonRpcResponse* pResponse)
{
   json::Array files;
   Error error = json::readParams(request.params, &files);
   if (error)
      return error;

   std::vector<FilePath> paths;
   std::transform(files.begin(), files.end(), std::back_inserter(paths),
                  &resolveAliasedJsonPath);

   error = runSvn(ShellArgs() << "add" << "--" << paths);
   if (error)
      return error;

   return Success();
}

Error svnDelete(const json::JsonRpcRequest& request,
                json::JsonRpcResponse* pResponse)
{
   json::Array files;
   Error error = json::readParams(request.params, &files);
   if (error)
      return error;

   std::vector<FilePath> paths;
   std::transform(files.begin(), files.end(), std::back_inserter(paths),
                  &resolveAliasedJsonPath);

   error = runSvn(ShellArgs() << "delete" << "--" << paths);
   if (error)
      return error;

   return Success();
}

Error svnRevert(const json::JsonRpcRequest& request,
                json::JsonRpcResponse* pResponse)
{
   json::Array files;
   Error error = json::readParams(request.params, &files);
   if (error)
      return error;

   std::vector<FilePath> paths;
   std::transform(files.begin(), files.end(), std::back_inserter(paths),
                  &resolveAliasedJsonPath);

   error = runSvn(ShellArgs() << "revert" << "--" << paths);
   if (error)
      return error;

   return Success();
}

Error svnStatus(const json::JsonRpcRequest& request,
                json::JsonRpcResponse* pResponse)
{
   std::string stdOut, stdErr;
   int exitCode;
   Error error = runSvn(
         ShellArgs() << "status" << "--xml" << "--ignore-externals",
         &stdOut,
         &stdErr,
         &exitCode);
   if (error)
      return error;

   if (exitCode != EXIT_SUCCESS)
   {
      LOG_ERROR_MESSAGE(stdErr);
      return Success();
   }

   std::vector<char> xmlData;
   xmlData.reserve(stdOut.size() + 1);
   std::copy(stdOut.begin(),
             stdOut.end(),
             std::back_inserter(xmlData));
   xmlData.push_back('\0'); // null terminator

   using namespace rapidxml;
   xml_document<> doc;
   doc.parse<0>(&(xmlData[0]));

   json::Array results;

   xml_node<>* pStatus = doc.first_node("status");
   if (pStatus)
   {
      FOREACH_NODE(pStatus, pList,)
      {
         FOREACH_NODE(pList, pEntry, "entry")
         {
            std::string path = attr_value(pEntry, "path");
            if (path.empty())
            {
               LOG_ERROR_MESSAGE("Path attribute not found");
               continue;
            }

            xml_node<>* pStatus = pEntry->first_node("wc-status");
            if (!pStatus)
            {
               LOG_ERROR_MESSAGE("Status node not found");
               continue;
            }

            std::string item = attr_value(pStatus, "item");
            if (item.empty())
            {
               LOG_ERROR_MESSAGE("Item attribute not found");
               continue;
            }
            item = translateItemStatus(item);

            std::string props = attr_value(pStatus, "props");
            if (props.empty())
            {
               LOG_ERROR_MESSAGE("Item properties not found");
               continue;
            }
            props = translateItemStatus(props);

            std::string status = topStatus(item, props);

            json::Object info;
            info["status"] = status;
            // TODO: escape path relative to <target>
            info["path"] = path;
            info["raw_path"] = module_context::createAliasedPath(
                  projects::projectContext().directory().childPath(path));
            results.push_back(info);
         }
      }
   }

   pResponse->setResult(results);

   return Success();
}

Error initialize()
{
   // install rpc methods
   using boost::bind;
   using namespace module_context;
   ExecBlock initBlock ;
   initBlock.addFunctions()
      (bind(registerRpcMethod, "svn_add", svnAdd))
      (bind(registerRpcMethod, "svn_delete", svnDelete))
      (bind(registerRpcMethod, "svn_revert", svnRevert))
      (bind(registerRpcMethod, "svn_status", svnStatus));
   Error error = initBlock.execute();
   if (error)
      return error;

   return Success();
}

Error initializeSvn(const core::FilePath& workingDir)
{
   s_workingDir = workingDir;
   return Success();
}

} // namespace svn
} // namespace modules
} //namespace session
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2016, Nils Asmussen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 *
 * 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.
 *
 * 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.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the FreeBSD Project.
 */

#include "cpu/dtu-accel-indir/accelerator.hh"
#include "debug/DtuAccelInDir.hh"
#include "debug/DtuAccelInDirState.hh"
#include "mem/dtu/dtu.hh"
#include "mem/dtu/regfile.hh"
#include "sim/dtu_memory.hh"

#include <iomanip>

static const char *stateNames[] =
{
    "IDLE",

    "FETCH_MSG",
    "READ_MSG_ADDR",
    "READ_MSG",

    "WRITE_DATA",
    "WRITE_DATA_WAIT",

    "STORE_REPLY",
    "SEND_REPLY",
    "REPLY_WAIT",
    "REPLY_ERROR",

    "CTXSW",

    "SYSCALL",
};

DtuAccelInDir::DtuAccelInDir(const DtuAccelInDirParams *p)
  : DtuAccel(p),
    bufSize(p->buf_size),
    irqPending(false),
    memPending(false),
    state(State::IDLE),
    lastState(State::CTXSW),    // something different
    msgAddr(),
    ctx(),
    sysc(this),
    yield(this, &sysc),
    ctxsw(this)
{
    static_assert((sizeof(stateNames) / sizeof(stateNames[0]) ==
                  static_cast<size_t>(State::SYSCALL) + 1), "Missmatch");
    yield.start();
}

std::string DtuAccelInDir::getStateName() const
{
    std::ostringstream os;
    os << stateNames[static_cast<size_t>(state)];
    if (state == State::IDLE)
        os << ":" << yield.stateName();
    else if (state == State::SYSCALL)
        os << ":" << sysc.stateName();
    else if (state == State::CTXSW)
        os << ":" << ctxsw.stateName();
    return os.str();
}

void
DtuAccelInDir::completeRequest(PacketPtr pkt)
{
    Request* req = pkt->req;

    if (state != lastState ||
        (state == State::CTXSW && ctxsw.hasStateChanged()) ||
        (state == State::SYSCALL && sysc.hasStateChanged()) ||
        (state == State::IDLE && yield.hasStateChanged()))
    {
        DPRINTF(DtuAccelInDirState, "[%s] Got response from memory\n",
            getStateName().c_str());
        lastState = state;
    }

    const uint8_t *pkt_data = pkt->getConstPtr<uint8_t>();

    Cycles delay(1);
    if (pkt->isError())
    {
        warn("%s access failed at %#x\n",
             pkt->isWrite() ? "Write" : "Read", req->getPaddr());
    }
    else
    {
        switch(state)
        {
            case State::IDLE:
            {
                if (yield.handleMemResp(pkt))
                {
                    if (irqPending)
                        irqPending = false;
                    state = State::CTXSW;
                }
                break;
            }

            case State::CTXSW:
            {
                if(ctxsw.handleMemResp(pkt))
                    state = State::FETCH_MSG;
                break;
            }

            case State::FETCH_MSG:
            {
                state = State::READ_MSG_ADDR;
                break;
            }
            case State::READ_MSG_ADDR:
            {
                const RegFile::reg_t *regs = pkt->getConstPtr<RegFile::reg_t>();
                if(regs[0])
                {
                    msgAddr = regs[0];
                    DPRINTF(DtuAccelInDir, "Received message @ %p\n", msgAddr);
                    state = State::READ_MSG;
                }
                else
                {
                    yield.start();
                    state = State::IDLE;
                }
                break;
            }
            case State::READ_MSG:
            {
                const uint64_t *args =
                    reinterpret_cast<const uint64_t*>(
                        pkt_data + sizeof(MessageHeader));

                DPRINTF(DtuAccelInDir, "  op=%d arg0=%#llx arg1=%#llx\n",
                    args[0], args[1], args[2]);

                dataSize = args[1];
                reply.msg.count = dataSize;

                if (static_cast<Operation>(args[0]) == Operation::COMPUTE)
                {
                    size_t compTime = args[2];
                    delay = Cycles((compTime * dataSize) / BLOCK_SIZE);
                    state = State::STORE_REPLY;
                }
                else
                    state = State::WRITE_DATA;
                break;
            }

            case State::WRITE_DATA:
            {
                state = State::WRITE_DATA_WAIT;
                break;
            }
            case State::WRITE_DATA_WAIT:
            {
                Dtu::Command::Bits cmd =
                    *reinterpret_cast<const RegFile::reg_t*>(pkt_data);
                if (cmd.opcode == 0)
                    state = State::STORE_REPLY;
                break;
            }

            case State::STORE_REPLY:
            {
                state = State::SEND_REPLY;
                break;
            }
            case State::SEND_REPLY:
            {
                state = State::REPLY_WAIT;
                break;
            }
            case State::REPLY_WAIT:
            {
                Dtu::Command::Bits cmd =
                    *reinterpret_cast<const RegFile::reg_t*>(pkt_data);
                if (cmd.opcode == 0)
                {
                    if (cmd.error == 0)
                        state = State::CTXSW;
                    else
                        state = State::REPLY_ERROR;
                }
                break;
            }
            case State::REPLY_ERROR:
            {
                sysc.start(sizeof(reply));
                syscNext = State::CTXSW;
                state = State::SYSCALL;
                break;
            }

            case State::SYSCALL:
            {
                if(sysc.handleMemResp(pkt))
                    state = syscNext;
                break;
            }
        }
    }

    memPending = false;
    freePacket(pkt);

    // kick things into action again
    schedule(tickEvent, clockEdge(delay));
}

void
DtuAccelInDir::wakeup()
{
    sysc.retryFetch();

    if (!memPending && !tickEvent.scheduled())
        schedule(tickEvent, clockEdge(Cycles(1)));
}

void
DtuAccelInDir::interrupt()
{
    irqPending = true;

    if (ctxsw.isWaiting())
    {
        ctxsw.restart();
        if (!tickEvent.scheduled())
            schedule(tickEvent, clockEdge(Cycles(1)));
    }
}

void
DtuAccelInDir::reset()
{
    irqPending = false;

    yield.start(false);
    state = State::IDLE;
    memset(&ctx, 0, sizeof(ctx));
}

void
DtuAccelInDir::tick()
{
    PacketPtr pkt = nullptr;

    if (state != lastState ||
        (state == State::CTXSW && ctxsw.hasStateChanged()) ||
        (state == State::SYSCALL && sysc.hasStateChanged()) ||
        (state == State::IDLE && yield.hasStateChanged()))
    {
        DPRINTF(DtuAccelInDirState, "[%s] tick\n",
            getStateName().c_str());
        lastState = state;
    }

    switch(state)
    {
        case State::IDLE:
        {
            pkt = yield.tick();
            break;
        }

        case State::CTXSW:
        {
            pkt = ctxsw.tick();
            break;
        }

        case State::FETCH_MSG:
        {
            if (irqPending)
            {
                irqPending = false;
                state = State::CTXSW;
                schedule(tickEvent, clockEdge(Cycles(1)));
            }
            else
            {
                Addr regAddr = getRegAddr(CmdReg::COMMAND);
                uint64_t value = Dtu::Command::FETCH_MSG | (EP_RECV << 4);
                pkt = createDtuRegPkt(regAddr, value, MemCmd::WriteReq);
            }
            break;
        }
        case State::READ_MSG_ADDR:
        {
            Addr regAddr = getRegAddr(CmdReg::OFFSET);
            pkt = createDtuRegPkt(regAddr, 0, MemCmd::ReadReq);
            break;
        }
        case State::READ_MSG:
        {
            pkt = createPacket(msgAddr, MSG_SIZE, MemCmd::ReadReq);
            break;
        }

        case State::WRITE_DATA:
        {
            pkt = createDtuCmdPkt(Dtu::Command::WRITE,
                                  EP_OUT,
                                  BUF_ADDR,
                                  dataSize,
                                  0);
            break;
        }
        case State::WRITE_DATA_WAIT:
        {
            Addr regAddr = getRegAddr(CmdReg::COMMAND);
            pkt = createDtuRegPkt(regAddr, 0, MemCmd::ReadReq);
            break;
        }

        case State::STORE_REPLY:
        {
            pkt = createPacket(BUF_ADDR,
                               sizeof(reply.msg),
                               MemCmd::WriteReq);
            memcpy(pkt->getPtr<uint8_t>(), (char*)&reply.msg, sizeof(reply.msg));
            break;
        }
        case State::SEND_REPLY:
        {
            pkt = createDtuCmdPkt(Dtu::Command::REPLY,
                                  EP_RECV,
                                  BUF_ADDR,
                                  sizeof(reply.msg),
                                  msgAddr);
            break;
        }
        case State::REPLY_WAIT:
        {
            Addr regAddr = getRegAddr(CmdReg::COMMAND);
            pkt = createDtuRegPkt(regAddr, 0, MemCmd::ReadReq);
            break;
        }
        case State::REPLY_ERROR:
        {
            reply.sys.opcode = SyscallSM::Operation::FORWARD_REPLY;
            reply.sys.cap = CAP_RBUF;
            reply.sys.msgaddr = msgAddr;
            reply.sys.event = 0;
            reply.sys.len = sizeof(reply.msg);
            reply.sys.event = 0;

            pkt = createPacket(MSG_ADDR, sizeof(reply), MemCmd::WriteReq);
            memcpy(pkt->getPtr<void>(), &reply, sizeof(reply));
            break;
        }

        case State::SYSCALL:
        {
            pkt = sysc.tick();
            break;
        }
    }

    if (pkt != nullptr)
    {
        memPending = true;
        sendPkt(pkt);
    }
}

DtuAccelInDir*
DtuAccelInDirParams::create()
{
    return new DtuAccelInDir(this);
}
<commit_msg>DtuAccelIndir: fixed bug.<commit_after>/*
 * Copyright (c) 2016, Nils Asmussen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 *
 * 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.
 *
 * 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.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the FreeBSD Project.
 */

#include "cpu/dtu-accel-indir/accelerator.hh"
#include "debug/DtuAccelInDir.hh"
#include "debug/DtuAccelInDirState.hh"
#include "mem/dtu/dtu.hh"
#include "mem/dtu/regfile.hh"
#include "sim/dtu_memory.hh"

#include <iomanip>

static const char *stateNames[] =
{
    "IDLE",

    "FETCH_MSG",
    "READ_MSG_ADDR",
    "READ_MSG",

    "WRITE_DATA",
    "WRITE_DATA_WAIT",

    "STORE_REPLY",
    "SEND_REPLY",
    "REPLY_WAIT",
    "REPLY_ERROR",

    "CTXSW",

    "SYSCALL",
};

DtuAccelInDir::DtuAccelInDir(const DtuAccelInDirParams *p)
  : DtuAccel(p),
    bufSize(p->buf_size),
    irqPending(false),
    memPending(false),
    state(State::IDLE),
    lastState(State::CTXSW),    // something different
    msgAddr(),
    ctx(),
    sysc(this),
    yield(this, &sysc),
    ctxsw(this)
{
    static_assert((sizeof(stateNames) / sizeof(stateNames[0]) ==
                  static_cast<size_t>(State::SYSCALL) + 1), "Missmatch");
    yield.start();
}

std::string DtuAccelInDir::getStateName() const
{
    std::ostringstream os;
    os << stateNames[static_cast<size_t>(state)];
    if (state == State::IDLE)
        os << ":" << yield.stateName();
    else if (state == State::SYSCALL)
        os << ":" << sysc.stateName();
    else if (state == State::CTXSW)
        os << ":" << ctxsw.stateName();
    return os.str();
}

void
DtuAccelInDir::completeRequest(PacketPtr pkt)
{
    Request* req = pkt->req;

    if (state != lastState ||
        (state == State::CTXSW && ctxsw.hasStateChanged()) ||
        (state == State::SYSCALL && sysc.hasStateChanged()) ||
        (state == State::IDLE && yield.hasStateChanged()))
    {
        DPRINTF(DtuAccelInDirState, "[%s] Got response from memory\n",
            getStateName().c_str());
        lastState = state;
    }

    const uint8_t *pkt_data = pkt->getConstPtr<uint8_t>();

    Cycles delay(1);
    if (pkt->isError())
    {
        warn("%s access failed at %#x\n",
             pkt->isWrite() ? "Write" : "Read", req->getPaddr());
    }
    else
    {
        switch(state)
        {
            case State::IDLE:
            {
                if (yield.handleMemResp(pkt))
                {
                    if (irqPending)
                        irqPending = false;
                    state = State::CTXSW;
                }
                break;
            }

            case State::CTXSW:
            {
                if(ctxsw.handleMemResp(pkt))
                    state = State::FETCH_MSG;
                break;
            }

            case State::FETCH_MSG:
            {
                state = State::READ_MSG_ADDR;
                break;
            }
            case State::READ_MSG_ADDR:
            {
                const RegFile::reg_t *regs = pkt->getConstPtr<RegFile::reg_t>();
                if(regs[0])
                {
                    msgAddr = regs[0];
                    DPRINTF(DtuAccelInDir, "Received message @ %p\n", msgAddr);
                    state = State::READ_MSG;
                }
                else
                {
                    yield.start();
                    state = State::IDLE;
                }
                break;
            }
            case State::READ_MSG:
            {
                const uint64_t *args =
                    reinterpret_cast<const uint64_t*>(
                        pkt_data + sizeof(MessageHeader));

                DPRINTF(DtuAccelInDir, "  op=%d arg0=%#llx arg1=%#llx\n",
                    args[0], args[1], args[2]);

                dataSize = args[1];
                reply.msg.count = dataSize;

                if (static_cast<Operation>(args[0]) == Operation::COMPUTE)
                {
                    size_t compTime = args[2];
                    delay = Cycles((compTime * dataSize) / BLOCK_SIZE);
                    state = State::STORE_REPLY;
                }
                else
                    state = State::WRITE_DATA;
                break;
            }

            case State::WRITE_DATA:
            {
                state = State::WRITE_DATA_WAIT;
                break;
            }
            case State::WRITE_DATA_WAIT:
            {
                Dtu::Command::Bits cmd =
                    *reinterpret_cast<const RegFile::reg_t*>(pkt_data);
                if (cmd.opcode == 0)
                    state = State::STORE_REPLY;
                break;
            }

            case State::STORE_REPLY:
            {
                state = State::SEND_REPLY;
                break;
            }
            case State::SEND_REPLY:
            {
                state = State::REPLY_WAIT;
                break;
            }
            case State::REPLY_WAIT:
            {
                Dtu::Command::Bits cmd =
                    *reinterpret_cast<const RegFile::reg_t*>(pkt_data);
                if (cmd.opcode == 0)
                {
                    if (cmd.error == 0)
                        state = State::CTXSW;
                    else
                        state = State::REPLY_ERROR;
                }
                break;
            }
            case State::REPLY_ERROR:
            {
                sysc.start(sizeof(reply));
                syscNext = State::CTXSW;
                state = State::SYSCALL;
                break;
            }

            case State::SYSCALL:
            {
                if(sysc.handleMemResp(pkt))
                    state = syscNext;
                break;
            }
        }
    }

    memPending = false;
    freePacket(pkt);

    // kick things into action again
    schedule(tickEvent, clockEdge(delay));
}

void
DtuAccelInDir::wakeup()
{
    sysc.retryFetch();

    if (!memPending && !tickEvent.scheduled())
        schedule(tickEvent, clockEdge(Cycles(1)));
}

void
DtuAccelInDir::interrupt()
{
    irqPending = true;

    if (ctxsw.isWaiting())
    {
        ctxsw.restart();
        if (!memPending && !tickEvent.scheduled())
            schedule(tickEvent, clockEdge(Cycles(1)));
    }
}

void
DtuAccelInDir::reset()
{
    irqPending = false;

    yield.start(false);
    state = State::IDLE;
    memset(&ctx, 0, sizeof(ctx));
}

void
DtuAccelInDir::tick()
{
    PacketPtr pkt = nullptr;

    if (state != lastState ||
        (state == State::CTXSW && ctxsw.hasStateChanged()) ||
        (state == State::SYSCALL && sysc.hasStateChanged()) ||
        (state == State::IDLE && yield.hasStateChanged()))
    {
        DPRINTF(DtuAccelInDirState, "[%s] tick\n",
            getStateName().c_str());
        lastState = state;
    }

    switch(state)
    {
        case State::IDLE:
        {
            pkt = yield.tick();
            break;
        }

        case State::CTXSW:
        {
            pkt = ctxsw.tick();
            break;
        }

        case State::FETCH_MSG:
        {
            if (irqPending)
            {
                irqPending = false;
                state = State::CTXSW;
                schedule(tickEvent, clockEdge(Cycles(1)));
            }
            else
            {
                Addr regAddr = getRegAddr(CmdReg::COMMAND);
                uint64_t value = Dtu::Command::FETCH_MSG | (EP_RECV << 4);
                pkt = createDtuRegPkt(regAddr, value, MemCmd::WriteReq);
            }
            break;
        }
        case State::READ_MSG_ADDR:
        {
            Addr regAddr = getRegAddr(CmdReg::OFFSET);
            pkt = createDtuRegPkt(regAddr, 0, MemCmd::ReadReq);
            break;
        }
        case State::READ_MSG:
        {
            pkt = createPacket(msgAddr, MSG_SIZE, MemCmd::ReadReq);
            break;
        }

        case State::WRITE_DATA:
        {
            pkt = createDtuCmdPkt(Dtu::Command::WRITE,
                                  EP_OUT,
                                  BUF_ADDR,
                                  dataSize,
                                  0);
            break;
        }
        case State::WRITE_DATA_WAIT:
        {
            Addr regAddr = getRegAddr(CmdReg::COMMAND);
            pkt = createDtuRegPkt(regAddr, 0, MemCmd::ReadReq);
            break;
        }

        case State::STORE_REPLY:
        {
            pkt = createPacket(BUF_ADDR,
                               sizeof(reply.msg),
                               MemCmd::WriteReq);
            memcpy(pkt->getPtr<uint8_t>(), (char*)&reply.msg, sizeof(reply.msg));
            break;
        }
        case State::SEND_REPLY:
        {
            pkt = createDtuCmdPkt(Dtu::Command::REPLY,
                                  EP_RECV,
                                  BUF_ADDR,
                                  sizeof(reply.msg),
                                  msgAddr);
            break;
        }
        case State::REPLY_WAIT:
        {
            Addr regAddr = getRegAddr(CmdReg::COMMAND);
            pkt = createDtuRegPkt(regAddr, 0, MemCmd::ReadReq);
            break;
        }
        case State::REPLY_ERROR:
        {
            reply.sys.opcode = SyscallSM::Operation::FORWARD_REPLY;
            reply.sys.cap = CAP_RBUF;
            reply.sys.msgaddr = msgAddr;
            reply.sys.event = 0;
            reply.sys.len = sizeof(reply.msg);
            reply.sys.event = 0;

            pkt = createPacket(MSG_ADDR, sizeof(reply), MemCmd::WriteReq);
            memcpy(pkt->getPtr<void>(), &reply, sizeof(reply));
            break;
        }

        case State::SYSCALL:
        {
            pkt = sysc.tick();
            break;
        }
    }

    if (pkt != nullptr)
    {
        memPending = true;
        sendPkt(pkt);
    }
}

DtuAccelInDir*
DtuAccelInDirParams::create()
{
    return new DtuAccelInDir(this);
}
<|endoftext|>


<commit_before>#pragma once

#include "cpputil.hh"
#include "ns.hh"
#include "gc.hh"
#include <atomic>
#include "refcache.hh"
#include "condvar.h"
#include "semaphore.h"
#include "seqlock.hh"
#include "mfs.hh"

class dirns;

u64 namehash(const strbuf<DIRSIZ>&);

struct file : public refcache::referenced, public rcu_freed {
  virtual int stat(struct stat*) { return -1; }
  virtual ssize_t read(char *addr, size_t n) { return -1; }
  virtual ssize_t write(const char *addr, size_t n) { return -1; }
  virtual ssize_t pread(char *addr, size_t n, off_t offset) { return -1; }
  virtual ssize_t pwrite(const char *addr, size_t n, off_t offset) { return -1; }

protected:
  file() : rcu_freed("file") {}

  void onzero() override = 0;
  void do_gc(void) override { delete this; }
};

struct file_inode : public file {
public:
  file_inode(sref<mnode> i, bool r, bool w, bool a)
    : ip(i), readable(r), writable(w), append(a), off(0) {}
  NEW_DELETE_OPS(file_inode);

  sref<mnode> ip;
  const bool readable;
  const bool writable;
  const bool append;
  u32 off;

  int stat(struct stat*) override;
  ssize_t read(char *addr, size_t n) override;
  ssize_t write(const char *addr, size_t n) override;
  ssize_t pread(char* addr, size_t n, off_t off) override;
  ssize_t pwrite(const char *addr, size_t n, off_t offset) override;
  void onzero() override;
};

struct file_socket : public file {
public:
  file_socket() : socket(0), localsock(nullptr),
                  wsem("file::wsem", 1), rsem("file::rsem", 1) {}
  NEW_DELETE_OPS(file_socket);

  int socket;
  struct localsock *localsock;

  ssize_t read(char *addr, size_t n) override;
  ssize_t write(const char *addr, size_t n) override;
  void onzero() override;

private:
  // XXX This locking should be handled in net, not here.
  semaphore wsem, rsem;
};

struct file_pipe_reader : public file {
public:
  file_pipe_reader(pipe* p) : pipe(p) {}
  NEW_DELETE_OPS(file_pipe_reader);

  ssize_t read(char *addr, size_t n) override;
  void onzero() override;

private:
  struct pipe* const pipe;
};

struct file_pipe_writer : public file {
public:
  file_pipe_writer(pipe* p) : pipe(p) {}
  NEW_DELETE_OPS(file_pipe_writer);

  ssize_t write(const char *addr, size_t n) override;
  void onzero() override;

private:
  struct pipe* const pipe;
};

// in-core file system types
struct inode : public referenced, public rcu_freed
{
  void  init();
  void  link();
  void  unlink();
  short nlink();

  inode& operator=(const inode&) = delete;
  inode(const inode& x) = delete;

  void do_gc() override { delete this; }

  // const for lifetime of object:
  const u32 dev;
  const u32 inum;

  // const unless inode is reused:
  u32 gen;
  std::atomic<short> type;
  short major;
  short minor;

  // locks for the rest of the inode
  seqcount<u64> seq;
  struct condvar cv;
  struct spinlock lock;
  char lockname[16];

  // initially null, set once:
  std::atomic<dirns*> dir;
  std::atomic<bool> valid;

  // protected by seq/lock:
  std::atomic<bool> busy;
  std::atomic<int> readbusy;

  u32 size;
  std::atomic<u32> addrs[NDIRECT+2];
  std::atomic<volatile u32*> iaddrs;
  short nlink_;

  // ??? what's the concurrency control plan?
  struct localsock *localsock;
  char socketpath[PATH_MAX];

private:
  inode(u32 dev, u32 inum);
  ~inode();
  NEW_DELETE_OPS(inode)

  static sref<inode> alloc(u32 dev, u32 inum);
  friend void initinode();
  friend sref<inode> iget(u32, u32);

protected:
  void onzero() override;
};

// device implementations

class mdev;

struct devsw {
  int (*read)(mdev*, char*, u32, u32);
  int (*write)(mdev*, const char*, u32, u32);
  void (*stat)(mdev*, struct stat*);
};

extern struct devsw devsw[];
<commit_msg>use different refcounting strategies for different kinds of files<commit_after>#pragma once

#include "cpputil.hh"
#include "ns.hh"
#include "gc.hh"
#include <atomic>
#include "refcache.hh"
#include "condvar.h"
#include "semaphore.h"
#include "seqlock.hh"
#include "mfs.hh"

class dirns;

u64 namehash(const strbuf<DIRSIZ>&);

struct file : public rcu_freed {
  virtual int stat(struct stat*) { return -1; }
  virtual ssize_t read(char *addr, size_t n) { return -1; }
  virtual ssize_t write(const char *addr, size_t n) { return -1; }
  virtual ssize_t pread(char *addr, size_t n, off_t offset) { return -1; }
  virtual ssize_t pwrite(const char *addr, size_t n, off_t offset) { return -1; }

  virtual void inc() = 0;
  virtual void dec() = 0;

protected:
  file() : rcu_freed("file") {}

  void do_gc(void) override { delete this; }
};

struct file_inode : public refcache::referenced, public file {
public:
  file_inode(sref<mnode> i, bool r, bool w, bool a)
    : ip(i), readable(r), writable(w), append(a), off(0) {}
  NEW_DELETE_OPS(file_inode);

  void inc() override { refcache::referenced::inc(); }
  void dec() override { refcache::referenced::dec(); }

  sref<mnode> ip;
  const bool readable;
  const bool writable;
  const bool append;
  u32 off;

  int stat(struct stat*) override;
  ssize_t read(char *addr, size_t n) override;
  ssize_t write(const char *addr, size_t n) override;
  ssize_t pread(char* addr, size_t n, off_t off) override;
  ssize_t pwrite(const char *addr, size_t n, off_t offset) override;
  void onzero() override;
};

struct file_socket : public referenced, public file {
public:
  file_socket() : socket(0), localsock(nullptr),
                  wsem("file::wsem", 1), rsem("file::rsem", 1) {}
  NEW_DELETE_OPS(file_socket);

  void inc() override { referenced::inc(); }
  void dec() override { referenced::dec(); }

  int socket;
  struct localsock *localsock;

  ssize_t read(char *addr, size_t n) override;
  ssize_t write(const char *addr, size_t n) override;
  void onzero() override;

private:
  // XXX This locking should be handled in net, not here.
  semaphore wsem, rsem;
};

struct file_pipe_reader : public refcache::referenced, public file {
public:
  file_pipe_reader(pipe* p) : pipe(p) {}
  NEW_DELETE_OPS(file_pipe_reader);

  void inc() override { refcache::referenced::inc(); }
  void dec() override { refcache::referenced::dec(); }

  ssize_t read(char *addr, size_t n) override;
  void onzero() override;

private:
  struct pipe* const pipe;
};

struct file_pipe_writer : public referenced, public file {
public:
  file_pipe_writer(pipe* p) : pipe(p) {}
  NEW_DELETE_OPS(file_pipe_writer);

  void inc() override { referenced::inc(); }
  void dec() override { referenced::dec(); }

  ssize_t write(const char *addr, size_t n) override;
  void onzero() override;

private:
  struct pipe* const pipe;
};

// in-core file system types
struct inode : public referenced, public rcu_freed
{
  void  init();
  void  link();
  void  unlink();
  short nlink();

  inode& operator=(const inode&) = delete;
  inode(const inode& x) = delete;

  void do_gc() override { delete this; }

  // const for lifetime of object:
  const u32 dev;
  const u32 inum;

  // const unless inode is reused:
  u32 gen;
  std::atomic<short> type;
  short major;
  short minor;

  // locks for the rest of the inode
  seqcount<u64> seq;
  struct condvar cv;
  struct spinlock lock;
  char lockname[16];

  // initially null, set once:
  std::atomic<dirns*> dir;
  std::atomic<bool> valid;

  // protected by seq/lock:
  std::atomic<bool> busy;
  std::atomic<int> readbusy;

  u32 size;
  std::atomic<u32> addrs[NDIRECT+2];
  std::atomic<volatile u32*> iaddrs;
  short nlink_;

  // ??? what's the concurrency control plan?
  struct localsock *localsock;
  char socketpath[PATH_MAX];

private:
  inode(u32 dev, u32 inum);
  ~inode();
  NEW_DELETE_OPS(inode)

  static sref<inode> alloc(u32 dev, u32 inum);
  friend void initinode();
  friend sref<inode> iget(u32, u32);

protected:
  void onzero() override;
};

// device implementations

class mdev;

struct devsw {
  int (*read)(mdev*, char*, u32, u32);
  int (*write)(mdev*, const char*, u32, u32);
  void (*stat)(mdev*, struct stat*);
};

extern struct devsw devsw[];
<|endoftext|>


<commit_before>/*
  Copyright (c) DataStax, Inc.

  Licensed 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 <gtest/gtest.h>

#include "cluster.hpp"
#include "query_request.hpp"
#include "mockssandra_test.hpp"
#include "session_base.hpp"

#define WAIT_FOR_TIME 5 * 1000 * 1000 // 5 seconds
#define KEYSPACE "datastax"

class TestSessionBase : public cass::SessionBase {
public:
  TestSessionBase()
    : connected_(0)
    , failed_(0)
    , closed_(0) { }

  virtual void on_up(const cass::Host::Ptr& host) { }
  virtual void on_down(const cass::Host::Ptr& host) { }
  virtual void on_add(const cass::Host::Ptr& host) { }
  virtual void on_remove(const cass::Host::Ptr& host) { }
  virtual void on_update_token_map(const cass::TokenMap::Ptr& token_map) { }

  int connected() { return connected_; }
  int failed() { return failed_; }
  int closed() { return closed_; }

protected:
  virtual void on_connect(const cass::Host::Ptr& connected_host,
                          int protocol_version,
                          const cass::HostMap& hosts,
                          const cass::TokenMap::Ptr& token_map) {
    ++connected_;
    ASSERT_STREQ("127.0.0.1", connected_host->address_string().c_str());
    ASSERT_EQ(CASS_HIGHEST_SUPPORTED_PROTOCOL_VERSION, protocol_version);
    ASSERT_EQ(1, hosts.size());
    ASSERT_EQ(state(), SESSION_STATE_CONNECTING);
    notify_connected();
    ASSERT_EQ(state(), SESSION_STATE_CONNECTED);
  }

  virtual void on_connect_failed(CassError code, const cass::String& message) {
    ++failed_;
    ASSERT_EQ(state(), SESSION_STATE_CONNECTING);
    notify_connect_failed(code, message);
    ASSERT_EQ(state(), SESSION_STATE_CLOSED);
  }

  virtual void on_close() {
    ++closed_;
    ASSERT_EQ(state(), SESSION_STATE_CLOSING);
    notify_closed();
  }

private:
  int connected_;
  int failed_;
  int closed_;
};

class SessionBaseUnitTest : public mockssandra::SimpleClusterTest { };

TEST_F(SessionBaseUnitTest, Simple) {
  start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_STREQ(KEYSPACE, session_base.connect_keyspace().c_str());
  ASSERT_NE(&session_base.config(), &config);
  ASSERT_TRUE(session_base.random() != NULL);
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, SimpleEmptyKeyspaceWithoutRandom) {
  start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  config.set_use_randomized_contact_points(false);
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, "", connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_TRUE(session_base.connect_keyspace().empty());
  ASSERT_NE(&session_base.config(), &config);
  ASSERT_TRUE(session_base.random() == NULL);
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, Ssl) {
  cass::ClusterSettings settings;
  settings.control_connection_settings.connection_settings = use_ssl();
  start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  config.set_ssl_context(settings.control_connection_settings.connection_settings.socket_settings.ssl_context.get());
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_STREQ(KEYSPACE, session_base.connect_keyspace().c_str());
  ASSERT_NE(&session_base.config(), &config);
  ASSERT_TRUE(session_base.random() != NULL);
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, SimpleInvalidContactPointsIp) {
  start_all();

  cass::Config config;
  config.set_use_randomized_contact_points(false);
  config.contact_points().push_back("123.456.789.012");
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_STREQ(KEYSPACE, session_base.connect_keyspace().c_str());
  ASSERT_NE(&session_base.config(), &config);
  ASSERT_TRUE(session_base.random() == NULL);
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, SimpleInvalidContactPointsHostname) {
  start_all();

  cass::Config config;
  config.contact_points().push_back("doesnotexist.dne");
  config.contact_points().push_back("localhost");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_STREQ(KEYSPACE, session_base.connect_keyspace().c_str());
  ASSERT_NE(&session_base.config(), &config);
  ASSERT_TRUE(session_base.random() != NULL);
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(1, session_base.connected());
  ASSERT_EQ(0, session_base.failed());
  ASSERT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, InvalidProtocol) {
  mockssandra::SimpleRequestHandlerBuilder builder;
  builder.with_supported_protocol_versions(0, 0); // Don't support any valid protocol version
  mockssandra::SimpleCluster cluster(builder.build(), 1);
  cluster.start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(CASS_ERROR_LIB_UNABLE_TO_DETERMINE_PROTOCOL, connect_future->error()->code);
  ASSERT_EQ(0, session_base.connected());
  ASSERT_EQ(1, session_base.failed());
  ASSERT_EQ(0, session_base.closed());
}

TEST_F(SessionBaseUnitTest, SslError) {
  cass::ClusterSettings settings;
  settings.control_connection_settings.connection_settings = use_ssl();
  start_all();

  cass::SslContext::Ptr invalid_ssl_context(cass::SslContextFactory::create());
  invalid_ssl_context->set_verify_flags(CASS_SSL_VERIFY_PEER_CERT);
  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  config.set_ssl_context(invalid_ssl_context.get());
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(CASS_ERROR_SSL_INVALID_PEER_CERT, connect_future->error()->code);
  ASSERT_EQ(0, session_base.connected());
  ASSERT_EQ(1, session_base.failed());
  ASSERT_EQ(0, session_base.closed());
}

TEST_F(SessionBaseUnitTest, BadCredentials) {
  mockssandra::SimpleCluster cluster(mockssandra::AuthRequestHandlerBuilder().build());
  cluster.start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(CASS_ERROR_SERVER_BAD_CREDENTIALS, connect_future->error()->code);
  ASSERT_EQ(0, session_base.connected());
  ASSERT_EQ(1, session_base.failed());
  ASSERT_EQ(0, session_base.closed());
}

TEST_F(SessionBaseUnitTest, NoHostsAvailable) {
  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE, connect_future->error()->code);
  ASSERT_EQ(0, session_base.connected());
  ASSERT_EQ(1, session_base.failed());
  ASSERT_EQ(0, session_base.closed());
}
<commit_msg>CPP-664 - Race condition in SessionBase unit tests<commit_after>/*
  Copyright (c) DataStax, Inc.

  Licensed 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 <gtest/gtest.h>

#include "cluster.hpp"
#include "query_request.hpp"
#include "mockssandra_test.hpp"
#include "session_base.hpp"

#define WAIT_FOR_TIME 5 * 1000 * 1000 // 5 seconds
#define KEYSPACE "datastax"

class TestSessionBase : public cass::SessionBase {
public:
  TestSessionBase()
    : connected_(0)
    , failed_(0)
    , closed_(0) { }

  virtual void on_up(const cass::Host::Ptr& host) { }
  virtual void on_down(const cass::Host::Ptr& host) { }
  virtual void on_add(const cass::Host::Ptr& host) { }
  virtual void on_remove(const cass::Host::Ptr& host) { }
  virtual void on_update_token_map(const cass::TokenMap::Ptr& token_map) { }

  int connected() { return connected_; }
  int failed() { return failed_; }
  int closed() { return closed_; }

protected:
  virtual void on_connect(const cass::Host::Ptr& connected_host,
                          int protocol_version,
                          const cass::HostMap& hosts,
                          const cass::TokenMap::Ptr& token_map) {
    ++connected_;
    ASSERT_STREQ("127.0.0.1", connected_host->address_string().c_str());
    ASSERT_EQ(CASS_HIGHEST_SUPPORTED_PROTOCOL_VERSION, protocol_version);
    ASSERT_EQ(1, hosts.size());
    ASSERT_EQ(state(), SESSION_STATE_CONNECTING);
    notify_connected();
  }

  virtual void on_connect_failed(CassError code, const cass::String& message) {
    ++failed_;
    ASSERT_EQ(state(), SESSION_STATE_CONNECTING);
    notify_connect_failed(code, message);
    ASSERT_EQ(state(), SESSION_STATE_CLOSED);
  }

  virtual void on_close() {
    ++closed_;
    ASSERT_EQ(state(), SESSION_STATE_CLOSING);
    notify_closed();
  }

private:
  int connected_;
  int failed_;
  int closed_;
};

class SessionBaseUnitTest : public mockssandra::SimpleClusterTest { };

TEST_F(SessionBaseUnitTest, Simple) {
  start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(session_base.state(), cass::SessionBase::SESSION_STATE_CONNECTED);
  EXPECT_STREQ(KEYSPACE, session_base.connect_keyspace().c_str());
  EXPECT_NE(&session_base.config(), &config);
  EXPECT_TRUE(session_base.random() != NULL);
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, SimpleEmptyKeyspaceWithoutRandom) {
  start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  config.set_use_randomized_contact_points(false);
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, "", connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(session_base.state(), cass::SessionBase::SESSION_STATE_CONNECTED);
  EXPECT_TRUE(session_base.connect_keyspace().empty());
  EXPECT_NE(&session_base.config(), &config);
  EXPECT_TRUE(session_base.random() == NULL);
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, Ssl) {
  cass::ClusterSettings settings;
  settings.control_connection_settings.connection_settings = use_ssl();
  start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  config.set_ssl_context(settings.control_connection_settings.connection_settings.socket_settings.ssl_context.get());
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  ASSERT_EQ(session_base.state(), cass::SessionBase::SESSION_STATE_CONNECTED);
  EXPECT_STREQ(KEYSPACE, session_base.connect_keyspace().c_str());
  EXPECT_NE(&session_base.config(), &config);
  EXPECT_TRUE(session_base.random() != NULL);
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, SimpleInvalidContactPointsIp) {
  start_all();

  cass::Config config;
  config.set_use_randomized_contact_points(false);
  config.contact_points().push_back("123.456.789.012");
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  EXPECT_STREQ(KEYSPACE, session_base.connect_keyspace().c_str());
  EXPECT_NE(&session_base.config(), &config);
  EXPECT_TRUE(session_base.random() == NULL);
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, SimpleInvalidContactPointsHostname) {
  start_all();

  cass::Config config;
  config.contact_points().push_back("doesnotexist.dne");
  config.contact_points().push_back("localhost");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  EXPECT_STREQ(KEYSPACE, session_base.connect_keyspace().c_str());
  EXPECT_NE(&session_base.config(), &config);
  EXPECT_TRUE(session_base.random() != NULL);
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(0, session_base.closed());

  cass::Future::Ptr close_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  session_base.close(close_future);
  ASSERT_TRUE(close_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(1, session_base.connected());
  EXPECT_EQ(0, session_base.failed());
  EXPECT_EQ(1, session_base.closed());
}

TEST_F(SessionBaseUnitTest, InvalidProtocol) {
  mockssandra::SimpleRequestHandlerBuilder builder;
  builder.with_supported_protocol_versions(0, 0); // Don't support any valid protocol version
  mockssandra::SimpleCluster cluster(builder.build(), 1);
  cluster.start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(CASS_ERROR_LIB_UNABLE_TO_DETERMINE_PROTOCOL, connect_future->error()->code);
  EXPECT_EQ(0, session_base.connected());
  EXPECT_EQ(1, session_base.failed());
  EXPECT_EQ(0, session_base.closed());
}

TEST_F(SessionBaseUnitTest, SslError) {
  cass::ClusterSettings settings;
  settings.control_connection_settings.connection_settings = use_ssl();
  start_all();

  cass::SslContext::Ptr invalid_ssl_context(cass::SslContextFactory::create());
  invalid_ssl_context->set_verify_flags(CASS_SSL_VERIFY_PEER_CERT);
  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  config.set_ssl_context(invalid_ssl_context.get());
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(CASS_ERROR_SSL_INVALID_PEER_CERT, connect_future->error()->code);
  EXPECT_EQ(0, session_base.connected());
  EXPECT_EQ(1, session_base.failed());
  EXPECT_EQ(0, session_base.closed());
}

TEST_F(SessionBaseUnitTest, BadCredentials) {
  mockssandra::SimpleCluster cluster(mockssandra::AuthRequestHandlerBuilder().build());
  cluster.start_all();

  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(CASS_ERROR_SERVER_BAD_CREDENTIALS, connect_future->error()->code);
  EXPECT_EQ(0, session_base.connected());
  EXPECT_EQ(1, session_base.failed());
  EXPECT_EQ(0, session_base.closed());
}

TEST_F(SessionBaseUnitTest, NoHostsAvailable) {
  cass::Config config;
  config.contact_points().push_back("127.0.0.1");
  cass::Future::Ptr connect_future(cass::Memory::allocate<cass::Future>(cass::Future::FUTURE_TYPE_SESSION));
  TestSessionBase session_base;

  session_base.connect(config, KEYSPACE, connect_future);
  ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
  EXPECT_EQ(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE, connect_future->error()->code);
  EXPECT_EQ(0, session_base.connected());
  EXPECT_EQ(1, session_base.failed());
  EXPECT_EQ(0, session_base.closed());
}
<|endoftext|>


<commit_before>/// \file TCanvasPainter.cxx
/// \ingroup CanvasPainter ROOT7
/// \author Axel Naumann <axel@cern.ch>
/// \date 2017-05-31
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is welcome!

/*************************************************************************
 * Copyright (C) 1995-2017, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#include "ROOT/TVirtualCanvasPainter.hxx"
#include "ROOT/TCanvas.hxx"
#include <ROOT/TLogger.hxx>
#include <ROOT/TDisplayItem.hxx>
#include <ROOT/TMenuItem.hxx>

#include <memory>
#include <string>
#include <vector>
#include <list>

#include "THttpEngine.h"
#include "THttpServer.h"
#include "TSystem.h"
#include "TList.h"
#include "TRandom.h"
#include "TPad.h"
#include "TROOT.h"
#include "TClass.h"
#include "TBufferJSON.h"

namespace {

/** \class TCanvasPainter
  Handles TCanvas communication with THttpServer.
  */

class TCanvasPainter: public THttpWSHandler,
                      public ROOT::Experimental::Internal::TVirtualCanvasPainter /*, THttpSocketListener*/ {
private:

   struct WebConn {
      THttpWSEngine  *fHandle;     ///<! websocket handle
      Bool_t          fReady;
      std::string     fGetMenu;    ///<! object id for menu request
      Bool_t          fModified;
      WebConn() : fHandle(0), fReady(kFALSE), fGetMenu(), fModified(kFALSE) {}
   };

  typedef std::list<WebConn> WebConnList;

  typedef std::vector<ROOT::Experimental::Detail::TMenuItem> MenuItemsVector;

  /// The canvas we are painting. It might go out of existence while painting.
  const ROOT::Experimental::TCanvas& fCanvas;

   WebConnList     fWebConn;      ///<! connections list
   ROOT::Experimental::TPadDisplayItem  fDisplayList; ///!< full list of items to display

   MenuItemsVector  fMenuItems;    ///<! list of menu items

   static std::string fAddr;
   static THttpServer *gServer;

  /// Disable copy construction.
  TCanvasPainter(const TCanvasPainter&) = delete;

  /// Disable assignment.
  TCanvasPainter& operator=(const TCanvasPainter&) = delete;

  ROOT::Experimental::Internal::TDrawable *FindDrawable(const ROOT::Experimental::TCanvas &can, const std::string &id);

  void CreateHttpServer();
  void PopupBrowser();
  void CheckModifiedFlag();
  std::string CreateSnapshot(const ROOT::Experimental::TCanvas& can);

  /// Send the canvas primitives to the THttpServer.
  // void SendCanvas();

   virtual Bool_t ProcessWS(THttpCallArg *arg);

public:
  /// Create a TVirtualCanvasPainter for the given canvas.
  /// The painter observes it; it needs to know should the TCanvas be deleted.
  TCanvasPainter(const std::string &name, const ROOT::Experimental::TCanvas& canv):
     THttpWSHandler(name.c_str(), "title"),
     fCanvas(canv),
     fWebConn()
   {
      CreateHttpServer();
      gServer->Register("/web7gui", this);
      PopupBrowser();
   }

   virtual void AddDisplayItem(ROOT::Experimental::TDisplayItem *item) final;

   virtual void AddMenuItem(const std::string &name, const std::string &title, const std::string &exec) final;

   virtual void AddChkMenuItem(const std::string &name, const std::string &title, bool checked, const std::string &toggle) final;

  // void ReactToSocketNews(...) override { SendCanvas(); }

/** \class CanvasPainterGenerator
    Creates TCanvasPainter objects.
  */

  class GeneratorImpl: public Generator {
  public:

    /// Create a new TCanvasPainter to paint the given TCanvas.
    std::unique_ptr<TVirtualCanvasPainter> Create(const ROOT::Experimental::TCanvas& canv) const override {
      return std::make_unique<TCanvasPainter>("name", canv);
    }
    ~GeneratorImpl() = default;

    /// Set TVirtualCanvasPainter::fgGenerator to a new GeneratorImpl object.
    static void SetGlobalPainter() {
      if (TVirtualCanvasPainter::fgGenerator) {
        R__ERROR_HERE("CanvasPainter") << "Generator is already set! Skipping second initialization.";
        return;
      }
      TVirtualCanvasPainter::fgGenerator.reset(new GeneratorImpl());
    }

    /// Release the GeneratorImpl object.
    static void ResetGlobalPainter() {
      TVirtualCanvasPainter::fgGenerator.reset();
    }
  };
};

std::string TCanvasPainter::fAddr = "";
THttpServer *TCanvasPainter::gServer = 0;

/** \class TCanvasPainterReg
  Registers TCanvasPainterGenerator as generator with ROOT::Experimental::Internal::TVirtualCanvasPainter.
  */
struct TCanvasPainterReg {
  TCanvasPainterReg() {
    TCanvasPainter::GeneratorImpl::SetGlobalPainter();
  }
  ~TCanvasPainterReg() {
    TCanvasPainter::GeneratorImpl::ResetGlobalPainter();
  }
} canvasPainterReg;

/// \}

} // unnamed namespace

void TCanvasPainter::CreateHttpServer()
{
   if (gServer) return;

   // gServer = new THttpServer("http:8080?loopback&websocket_timeout=10000");
   const char *port = gSystem->Getenv("WEBGUI_PORT");
   TString buf;
   if (!port) {
      gRandom->SetSeed(0);
      buf.Form("%d", (int) (8800 + 1000* gRandom->Rndm(1)));
      port = buf.Data(); // "8181";
   }
   fAddr = TString::Format("http://localhost:%s", port).Data();
   gServer = new THttpServer(TString::Format("http:%s?websocket_timeout=10000", port).Data());
}

void TCanvasPainter::PopupBrowser()
{
   TString addr, exec;

   addr.Form("%s/web7gui/%s/draw.htm?webcanvas", fAddr.c_str(), GetName());

   if (gSystem->InheritsFrom("TMacOSXSystem"))
      exec.Form("open %s", addr.Data());
   else
      exec.Form("xdg-open %s &", addr.Data());
   printf("Exec %s\n", exec.Data());

   gSystem->Exec(exec);
}

Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg)
{
   if (!arg) return kTRUE;

   // try to identify connection for given WS request
   WebConn* conn = 0;
   WebConnList::iterator iter = fWebConn.begin();
   while (iter != fWebConn.end()) {
      if (iter->fHandle && (iter->fHandle->GetId() == arg->GetWSId()) && arg->GetWSId()) {
         conn = &(*iter); break;
      }
      ++iter;
   }

   if (strcmp(arg->GetMethod(),"WS_CONNECT")==0) {

      // accept all requests, in future one could limit number of connections
      // arg->Set404(); // refuse connection
      return kTRUE;
   }

   if (strcmp(arg->GetMethod(),"WS_READY")==0) {
      THttpWSEngine* wshandle = dynamic_cast<THttpWSEngine*> (arg->TakeWSHandle());

      if (conn != 0) Error("ProcessWSRequest","WSHandle with given websocket id exists!!!");

      WebConn newconn;
      newconn.fHandle = wshandle;
      newconn.fModified = kTRUE;

      fWebConn.push_back(newconn);
      printf("socket is ready\n");

      return kTRUE;
   }

   if (strcmp(arg->GetMethod(),"WS_CLOSE")==0) {
      // connection is closed, one can remove handle

      if (conn && conn->fHandle) {
         conn->fHandle->ClearHandle();
         delete conn->fHandle;
         conn->fHandle = 0;
      }

      if (conn) fWebConn.erase(iter);
      return kTRUE;
   }

   if (strcmp(arg->GetMethod(),"WS_DATA")!=0) {
      Error("ProcessWSRequest","WSHandle DATA request expected!");
      return kFALSE;
   }

   if (!conn) {
      Error("ProcessWSRequest","Get websocket data without valid connection - ignore!!!");
      return kFALSE;
   }

   if (conn->fHandle->PreviewData(arg)) return kTRUE;

   const char* cdata = (arg->GetPostDataLength()<=0) ? "" : (const char*) arg->GetPostData();

   if (strncmp(cdata,"READY",5)==0) {
      conn->fReady = kTRUE;
      CheckModifiedFlag();
   } else if (strncmp(cdata, "RREADY:", 7)==0) {
      conn->fReady = kTRUE;
      CheckModifiedFlag();
   } else if (strncmp(cdata,"GETMENU:",8)==0) {
      conn->fReady = kTRUE;
      conn->fGetMenu = cdata+8;
      CheckModifiedFlag();
   } else if (strncmp(cdata,"GEXE:",5)==0) {
      // TODO: temporary solution, should be removed later
      // used now to terminate ROOT session
      gROOT->ProcessLine(cdata+5);
   } else if (strncmp(cdata,"OBJEXEC:",8)==0) {
      TString buf(cdata+8);
      Int_t pos = buf.First(':');

      if (pos>0) {
         TString id = buf(0,pos);
         buf.Remove(0, pos+1);
         ROOT::Experimental::Internal::TDrawable *drawable = FindDrawable(fCanvas, id.Data());
         if (drawable && (buf.Length()>0)) {
            printf("Execute %s for drawable %p\n", buf.Data(), drawable);
            drawable->Execute(buf.Data());
         }
      }
   }

   return kTRUE;
}

void TCanvasPainter::AddMenuItem(const std::string &name, const std::string &title, const std::string &exec)
{
    ROOT::Experimental::Detail::TMenuItem item(name, title);
    item.SetExec(exec);
    fMenuItems.push_back(item);
}

void TCanvasPainter::AddChkMenuItem(const std::string &name, const std::string &title, bool checked, const std::string &toggle)
{
   ROOT::Experimental::Detail::TMenuItem item(name, title);
   item.SetChecked(checked);
   item.SetExec(toggle);
   fMenuItems.push_back(item);
}

void TCanvasPainter::CheckModifiedFlag()
{

   for (WebConnList::iterator citer = fWebConn.begin(); citer != fWebConn.end(); ++citer) {
      WebConn& conn = *citer;

      if (!conn.fReady || !conn.fHandle) continue;

      TString buf;

      if (!conn.fGetMenu.empty()) {
         ROOT::Experimental::Internal::TDrawable *drawable = FindDrawable(fCanvas, conn.fGetMenu);

         printf("Request menu for object %s found drawable %p\n", conn.fGetMenu.c_str(), drawable);

         if (drawable) {
            fMenuItems.clear();
            drawable->PopulateMenu(*this);

            TClass *cl = gROOT->GetClass("std::vector<ROOT::Experimental::Detail::TMenuItem>");

            // printf("Got items %d class %p %s\n", (int) fMenuItems.size(), cl, cl->GetName());

            // FIXME: got problem with std::list<TMenuItem>, can be generic TBufferJSON
            buf = "MENU";
            buf += TBufferJSON::ConvertToJSON(&fMenuItems, cl);

            fMenuItems.clear();
         }

         conn.fGetMenu = "";
      } else
       if (conn.fModified) {
         // buf = "JSON";
         // buf  += TBufferJSON::ConvertToJSON(Canvas(), 3);

         buf = "SNAP";
         buf += CreateSnapshot(fCanvas);
         conn.fModified = kFALSE;
      }

      if (buf.Length() > 0) {
         // sending of data can be moved into separate thread - not to block user code
         conn.fReady = kFALSE;
         conn.fHandle->SendCharStar(buf.Data());
      }
   }
}

void TCanvasPainter::AddDisplayItem(ROOT::Experimental::TDisplayItem *item)
{
   fDisplayList.Add(item);
}

ROOT::Experimental::Internal::TDrawable *TCanvasPainter::FindDrawable(const ROOT::Experimental::TCanvas &can, const std::string &id)
{

   for (auto &&drawable: can.GetPrimitives()) {

      std::string subid = ROOT::Experimental::TDisplayItem::MakeIDFromPtr(&(*drawable));

      if (subid == id) return &(*drawable);
   }

   return nullptr;
}

std::string TCanvasPainter::CreateSnapshot(const ROOT::Experimental::TCanvas& can)
{

   fDisplayList.Clear();

   fDisplayList.SetObjectIDAsPtr((void*)&can);

   TPad *dummy = new TPad(); // just to keep information we need for different ranges now

   auto *snap = new ROOT::Experimental::TUniqueDisplayItem<TPad>(dummy);
   snap->SetObjectIDAsPtr((void*)&can);
   fDisplayList.Add(snap);

   for (auto &&drawable: can.GetPrimitives()) {

      drawable->Paint(*this);

      fDisplayList.Last()->SetObjectIDAsPtr(&(*drawable));

      // ROOT::Experimental::TDisplayItem *sub = drawable->CreateSnapshot(can);
      // if (!sub) continue;
      // sub->SetObjectIDAsPtr(&(*drawable));
      // lst.Add(sub);
    }

   TString res = TBufferJSON::ConvertToJSON(&fDisplayList, gROOT->GetClass("ROOT::Experimental::TPadDisplayItem"));

   fDisplayList.Clear();

   // printf("JSON %s\n", res.Data());

   return std::string(res.Data());
}

//void TCanvasPainter::SendCanvas() {
//  for (auto &&drawable: fCanvas.GetPrimitives()) {
//    drawable->Paint(*this);
//  }
//}
<commit_msg>webgui: avoid string copy in canvas painter<commit_after>/// \file TCanvasPainter.cxx
/// \ingroup CanvasPainter ROOT7
/// \author Axel Naumann <axel@cern.ch>
/// \date 2017-05-31
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is welcome!

/*************************************************************************
 * Copyright (C) 1995-2017, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#include "ROOT/TVirtualCanvasPainter.hxx"
#include "ROOT/TCanvas.hxx"
#include <ROOT/TLogger.hxx>
#include <ROOT/TDisplayItem.hxx>
#include <ROOT/TMenuItem.hxx>

#include <memory>
#include <string>
#include <vector>
#include <list>

#include "THttpEngine.h"
#include "THttpServer.h"
#include "TSystem.h"
#include "TList.h"
#include "TRandom.h"
#include "TPad.h"
#include "TROOT.h"
#include "TClass.h"
#include "TBufferJSON.h"

namespace {

/** \class TCanvasPainter
  Handles TCanvas communication with THttpServer.
  */

class TCanvasPainter: public THttpWSHandler,
                      public ROOT::Experimental::Internal::TVirtualCanvasPainter /*, THttpSocketListener*/ {
private:

   struct WebConn {
      THttpWSEngine  *fHandle;     ///<! websocket handle
      Bool_t          fReady;
      std::string     fGetMenu;    ///<! object id for menu request
      Bool_t          fModified;
      WebConn() : fHandle(0), fReady(kFALSE), fGetMenu(), fModified(kFALSE) {}
   };

  typedef std::list<WebConn> WebConnList;

  typedef std::vector<ROOT::Experimental::Detail::TMenuItem> MenuItemsVector;

  /// The canvas we are painting. It might go out of existence while painting.
  const ROOT::Experimental::TCanvas& fCanvas;

   WebConnList     fWebConn;      ///<! connections list
   ROOT::Experimental::TPadDisplayItem  fDisplayList; ///!< full list of items to display

   MenuItemsVector  fMenuItems;    ///<! list of menu items

   static std::string fAddr;
   static THttpServer *gServer;

  /// Disable copy construction.
  TCanvasPainter(const TCanvasPainter&) = delete;

  /// Disable assignment.
  TCanvasPainter& operator=(const TCanvasPainter&) = delete;

  ROOT::Experimental::Internal::TDrawable *FindDrawable(const ROOT::Experimental::TCanvas &can, const std::string &id);

  void CreateHttpServer();
  void PopupBrowser();
  void CheckModifiedFlag();
  std::string CreateSnapshot(const ROOT::Experimental::TCanvas& can);

  /// Send the canvas primitives to the THttpServer.
  // void SendCanvas();

   virtual Bool_t ProcessWS(THttpCallArg *arg);

public:
  /// Create a TVirtualCanvasPainter for the given canvas.
  /// The painter observes it; it needs to know should the TCanvas be deleted.
  TCanvasPainter(const std::string &name, const ROOT::Experimental::TCanvas& canv):
     THttpWSHandler(name.c_str(), "title"),
     fCanvas(canv),
     fWebConn()
   {
      CreateHttpServer();
      gServer->Register("/web7gui", this);
      PopupBrowser();
   }

   virtual void AddDisplayItem(ROOT::Experimental::TDisplayItem *item) final;

   virtual void AddMenuItem(const std::string &name, const std::string &title, const std::string &exec) final;

   virtual void AddChkMenuItem(const std::string &name, const std::string &title, bool checked, const std::string &toggle) final;

  // void ReactToSocketNews(...) override { SendCanvas(); }

/** \class CanvasPainterGenerator
    Creates TCanvasPainter objects.
  */

  class GeneratorImpl: public Generator {
  public:

    /// Create a new TCanvasPainter to paint the given TCanvas.
    std::unique_ptr<TVirtualCanvasPainter> Create(const ROOT::Experimental::TCanvas& canv) const override {
      return std::make_unique<TCanvasPainter>("name", canv);
    }
    ~GeneratorImpl() = default;

    /// Set TVirtualCanvasPainter::fgGenerator to a new GeneratorImpl object.
    static void SetGlobalPainter() {
      if (TVirtualCanvasPainter::fgGenerator) {
        R__ERROR_HERE("CanvasPainter") << "Generator is already set! Skipping second initialization.";
        return;
      }
      TVirtualCanvasPainter::fgGenerator.reset(new GeneratorImpl());
    }

    /// Release the GeneratorImpl object.
    static void ResetGlobalPainter() {
      TVirtualCanvasPainter::fgGenerator.reset();
    }
  };
};

std::string TCanvasPainter::fAddr = "";
THttpServer *TCanvasPainter::gServer = 0;

/** \class TCanvasPainterReg
  Registers TCanvasPainterGenerator as generator with ROOT::Experimental::Internal::TVirtualCanvasPainter.
  */
struct TCanvasPainterReg {
  TCanvasPainterReg() {
    TCanvasPainter::GeneratorImpl::SetGlobalPainter();
  }
  ~TCanvasPainterReg() {
    TCanvasPainter::GeneratorImpl::ResetGlobalPainter();
  }
} canvasPainterReg;

/// \}

} // unnamed namespace

void TCanvasPainter::CreateHttpServer()
{
   if (gServer) return;

   // gServer = new THttpServer("http:8080?loopback&websocket_timeout=10000");
   const char *port = gSystem->Getenv("WEBGUI_PORT");
   TString buf;
   if (!port) {
      gRandom->SetSeed(0);
      buf.Form("%d", (int) (8800 + 1000* gRandom->Rndm(1)));
      port = buf.Data(); // "8181";
   }
   fAddr = TString::Format("http://localhost:%s", port).Data();
   gServer = new THttpServer(TString::Format("http:%s?websocket_timeout=10000", port).Data());
}

void TCanvasPainter::PopupBrowser()
{
   TString addr, exec;

   addr.Form("%s/web7gui/%s/draw.htm?webcanvas", fAddr.c_str(), GetName());

   if (gSystem->InheritsFrom("TMacOSXSystem"))
      exec.Form("open %s", addr.Data());
   else
      exec.Form("xdg-open %s &", addr.Data());
   printf("Exec %s\n", exec.Data());

   gSystem->Exec(exec);
}

Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg)
{
   if (!arg) return kTRUE;

   // try to identify connection for given WS request
   WebConn* conn = 0;
   WebConnList::iterator iter = fWebConn.begin();
   while (iter != fWebConn.end()) {
      if (iter->fHandle && (iter->fHandle->GetId() == arg->GetWSId()) && arg->GetWSId()) {
         conn = &(*iter); break;
      }
      ++iter;
   }

   if (strcmp(arg->GetMethod(),"WS_CONNECT")==0) {

      // accept all requests, in future one could limit number of connections
      // arg->Set404(); // refuse connection
      return kTRUE;
   }

   if (strcmp(arg->GetMethod(),"WS_READY")==0) {
      THttpWSEngine* wshandle = dynamic_cast<THttpWSEngine*> (arg->TakeWSHandle());

      if (conn != 0) Error("ProcessWSRequest","WSHandle with given websocket id exists!!!");

      WebConn newconn;
      newconn.fHandle = wshandle;
      newconn.fModified = kTRUE;

      fWebConn.push_back(newconn);
      printf("socket is ready\n");

      return kTRUE;
   }

   if (strcmp(arg->GetMethod(),"WS_CLOSE")==0) {
      // connection is closed, one can remove handle

      if (conn && conn->fHandle) {
         conn->fHandle->ClearHandle();
         delete conn->fHandle;
         conn->fHandle = 0;
      }

      if (conn) fWebConn.erase(iter);
      return kTRUE;
   }

   if (strcmp(arg->GetMethod(),"WS_DATA")!=0) {
      Error("ProcessWSRequest","WSHandle DATA request expected!");
      return kFALSE;
   }

   if (!conn) {
      Error("ProcessWSRequest","Get websocket data without valid connection - ignore!!!");
      return kFALSE;
   }

   if (conn->fHandle->PreviewData(arg)) return kTRUE;

   const char* cdata = (arg->GetPostDataLength()<=0) ? "" : (const char*) arg->GetPostData();

   if (strncmp(cdata,"READY",5)==0) {
      conn->fReady = kTRUE;
      CheckModifiedFlag();
   } else if (strncmp(cdata, "RREADY:", 7)==0) {
      conn->fReady = kTRUE;
      CheckModifiedFlag();
   } else if (strncmp(cdata,"GETMENU:",8)==0) {
      conn->fReady = kTRUE;
      conn->fGetMenu = cdata+8;
      CheckModifiedFlag();
   } else if (strncmp(cdata,"GEXE:",5)==0) {
      // TODO: temporary solution, should be removed later
      // used now to terminate ROOT session
      gROOT->ProcessLine(cdata+5);
   } else if (strncmp(cdata,"OBJEXEC:",8)==0) {
      TString buf(cdata+8);
      Int_t pos = buf.First(':');

      if (pos>0) {
         TString id = buf(0,pos);
         buf.Remove(0, pos+1);
         ROOT::Experimental::Internal::TDrawable *drawable = FindDrawable(fCanvas, id.Data());
         if (drawable && (buf.Length()>0)) {
            printf("Execute %s for drawable %p\n", buf.Data(), drawable);
            drawable->Execute(buf.Data());
         }
      }
   }

   return kTRUE;
}

void TCanvasPainter::AddMenuItem(const std::string &name, const std::string &title, const std::string &exec)
{
    ROOT::Experimental::Detail::TMenuItem item(name, title);
    item.SetExec(exec);
    fMenuItems.push_back(item);
}

void TCanvasPainter::AddChkMenuItem(const std::string &name, const std::string &title, bool checked, const std::string &toggle)
{
   ROOT::Experimental::Detail::TMenuItem item(name, title);
   item.SetChecked(checked);
   item.SetExec(toggle);
   fMenuItems.push_back(item);
}

void TCanvasPainter::CheckModifiedFlag()
{

   for (WebConnList::iterator citer = fWebConn.begin(); citer != fWebConn.end(); ++citer) {
      WebConn& conn = *citer;

      if (!conn.fReady || !conn.fHandle) continue;

      TString buf;

      if (!conn.fGetMenu.empty()) {
         ROOT::Experimental::Internal::TDrawable *drawable = FindDrawable(fCanvas, conn.fGetMenu);

         printf("Request menu for object %s found drawable %p\n", conn.fGetMenu.c_str(), drawable);

         if (drawable) {
            fMenuItems.clear();
            drawable->PopulateMenu(*this);

            TClass *cl = gROOT->GetClass("std::vector<ROOT::Experimental::Detail::TMenuItem>");

            // printf("Got items %d class %p %s\n", (int) fMenuItems.size(), cl, cl->GetName());

            // FIXME: got problem with std::list<TMenuItem>, can be generic TBufferJSON
            buf = "MENU";
            buf += TBufferJSON::ConvertToJSON(&fMenuItems, cl);

            fMenuItems.clear();
         }

         conn.fGetMenu = "";
      } else
       if (conn.fModified) {
         // buf = "JSON";
         // buf  += TBufferJSON::ConvertToJSON(Canvas(), 3);

         buf = "SNAP";
         buf += CreateSnapshot(fCanvas);
         conn.fModified = kFALSE;
      }

      if (buf.Length() > 0) {
         // sending of data can be moved into separate thread - not to block user code
         conn.fReady = kFALSE;
         conn.fHandle->SendCharStar(buf.Data());
      }
   }
}

void TCanvasPainter::AddDisplayItem(ROOT::Experimental::TDisplayItem *item)
{
   fDisplayList.Add(item);
}

ROOT::Experimental::Internal::TDrawable *TCanvasPainter::FindDrawable(const ROOT::Experimental::TCanvas &can, const std::string &id)
{

   for (auto &&drawable: can.GetPrimitives()) {

      if (id == ROOT::Experimental::TDisplayItem::MakeIDFromPtr(&(*drawable)))
         return &(*drawable);

   }

   return nullptr;
}

std::string TCanvasPainter::CreateSnapshot(const ROOT::Experimental::TCanvas& can)
{

   fDisplayList.Clear();

   fDisplayList.SetObjectIDAsPtr((void*)&can);

   TPad *dummy = new TPad(); // just to keep information we need for different ranges now

   auto *snap = new ROOT::Experimental::TUniqueDisplayItem<TPad>(dummy);
   snap->SetObjectIDAsPtr((void*)&can);
   fDisplayList.Add(snap);

   for (auto &&drawable: can.GetPrimitives()) {

      drawable->Paint(*this);

      fDisplayList.Last()->SetObjectIDAsPtr(&(*drawable));

      // ROOT::Experimental::TDisplayItem *sub = drawable->CreateSnapshot(can);
      // if (!sub) continue;
      // sub->SetObjectIDAsPtr(&(*drawable));
      // lst.Add(sub);
    }

   TString res = TBufferJSON::ConvertToJSON(&fDisplayList, gROOT->GetClass("ROOT::Experimental::TPadDisplayItem"));

   fDisplayList.Clear();

   // printf("JSON %s\n", res.Data());

   return std::string(res.Data());
}

//void TCanvasPainter::SendCanvas() {
//  for (auto &&drawable: fCanvas.GetPrimitives()) {
//    drawable->Paint(*this);
//  }
//}
<|endoftext|>