| /* | |
| * Copyright (c) 2014-2017 ARM Limited | |
| * All rights reserved | |
| * | |
| * The license below extends only to copyright in the software and shall | |
| * not be construed as granting a license to any other intellectual | |
| * property including but not limited to intellectual property relating | |
| * to a hardware implementation of the functionality of the software | |
| * licensed hereunder. You may use the software subject to the license | |
| * terms below provided that you ensure that this notice is replicated | |
| * unmodified and in its entirety in all distributions of the software, | |
| * modified or unmodified, in source code or in binary form. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are | |
| * met: redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer; | |
| * 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; | |
| * neither the name of the copyright holders nor the names of its | |
| * contributors may be used to endorse or promote products derived from | |
| * this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| * | |
| * Authors: Andreas Sandberg | |
| */ | |
| #include "dev/virtio/fs9p.hh" | |
| #include <fcntl.h> | |
| #include <netdb.h> | |
| #include <netinet/in.h> | |
| #include <sys/socket.h> | |
| #include <sys/types.h> | |
| #include <sys/un.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #include <csignal> | |
| #include <fstream> | |
| #include "base/callback.hh" | |
| #include "base/output.hh" | |
| #include "debug/VIO9P.hh" | |
| #include "debug/VIO9PData.hh" | |
| #include "params/VirtIO9PBase.hh" | |
| #include "params/VirtIO9PDiod.hh" | |
| #include "params/VirtIO9PProxy.hh" | |
| #include "params/VirtIO9PSocket.hh" | |
| #include "sim/system.hh" | |
| struct P9MsgInfo { | |
| P9MsgInfo(P9MsgType _type, std::string _name) | |
| : type(_type), name(_name) {} | |
| P9MsgType type; | |
| std::string name; | |
| }; | |
| typedef std::map<P9MsgType, P9MsgInfo> P9MsgInfoMap; | |
| #define P9MSG(type, name) \ | |
| { (type), P9MsgInfo((type), "T" # name ) }, \ | |
| { (type + 1), P9MsgInfo((type + 1), "R" # name ) } | |
| static const P9MsgInfoMap p9_msg_info { | |
| P9MSG(6, LERROR), | |
| P9MSG(8, STATFS), | |
| P9MSG(12, LOPEN), | |
| P9MSG(14, LCREATE), | |
| P9MSG(16, SYMLINK), | |
| P9MSG(18, MKNOD), | |
| P9MSG(20, RENAME), | |
| P9MSG(22, READLINK), | |
| P9MSG(24, GETATTR), | |
| P9MSG(26, SETATTR), | |
| P9MSG(30, XATTRWALK), | |
| P9MSG(32, XATTRCREATE), | |
| P9MSG(40, READDIR), | |
| P9MSG(50, FSYNC), | |
| P9MSG(52, LOCK), | |
| P9MSG(54, GETLOCK), | |
| P9MSG(70, LINK), | |
| P9MSG(72, MKDIR), | |
| P9MSG(74, RENAMEAT), | |
| P9MSG(76, UNLINKAT), | |
| P9MSG(100, VERSION), | |
| P9MSG(102, AUTH), | |
| P9MSG(104, ATTACH), | |
| P9MSG(106, ERROR), | |
| P9MSG(108, FLUSH), | |
| P9MSG(110, WALK), | |
| P9MSG(112, OPEN), | |
| P9MSG(114, CREATE), | |
| P9MSG(116, READ), | |
| P9MSG(118, WRITE), | |
| P9MSG(120, CLUNK), | |
| P9MSG(122, REMOVE), | |
| P9MSG(124, STAT), | |
| P9MSG(126, WSTAT), | |
| }; | |
| #undef P9MSG | |
| VirtIO9PBase::VirtIO9PBase(Params *params) | |
| : VirtIODeviceBase(params, ID_9P, | |
| sizeof(Config) + params->tag.size(), | |
| F_MOUNT_TAG), | |
| queue(params->system->physProxy, params->queueSize, *this) | |
| { | |
| config.reset((Config *) | |
| operator new(configSize)); | |
| config->len = htov_legacy(params->tag.size()); | |
| memcpy(config->tag, params->tag.c_str(), params->tag.size()); | |
| registerQueue(queue); | |
| } | |
| VirtIO9PBase::~VirtIO9PBase() | |
| { | |
| } | |
| void | |
| VirtIO9PBase::readConfig(PacketPtr pkt, Addr cfgOffset) | |
| { | |
| readConfigBlob(pkt, cfgOffset, (uint8_t *)config.get()); | |
| } | |
| void | |
| VirtIO9PBase::FSQueue::onNotifyDescriptor(VirtDescriptor *desc) | |
| { | |
| DPRINTF(VIO9P, "Got input data descriptor (len: %i)\n", desc->size()); | |
| DPRINTF(VIO9P, "\tPending transactions: %i\n", parent.pendingTransactions.size()); | |
| P9MsgHeader header; | |
| desc->chainRead(0, (uint8_t *)&header, sizeof(header)); | |
| header = p9toh(header); | |
| uint8_t data[header.len - sizeof(header)]; | |
| desc->chainRead(sizeof(header), data, sizeof(data)); | |
| // Keep track of pending transactions | |
| parent.pendingTransactions[header.tag] = desc; | |
| DPRINTF(VIO9P, "recvTMsg\n"); | |
| parent.dumpMsg(header, data, sizeof(data)); | |
| // Notify device of message | |
| parent.recvTMsg(header, data, sizeof(data)); | |
| } | |
| void | |
| VirtIO9PBase::sendRMsg(const P9MsgHeader &header, const uint8_t *data, size_t size) | |
| { | |
| DPRINTF(VIO9P, "Sending RMsg\n"); | |
| dumpMsg(header, data, size); | |
| DPRINTF(VIO9P, "\tPending transactions: %i\n", pendingTransactions.size()); | |
| assert(header.len >= sizeof(header)); | |
| VirtDescriptor *main_desc(pendingTransactions[header.tag]); | |
| pendingTransactions.erase(header.tag); | |
| // Find the first output descriptor | |
| VirtDescriptor *out_desc(main_desc); | |
| while (out_desc && !out_desc->isOutgoing()) | |
| out_desc = out_desc->next(); | |
| if (!out_desc) | |
| panic("sendRMsg: Framing error, no output descriptor.\n"); | |
| P9MsgHeader header_out(htop9(header)); | |
| header_out.len = htop9(sizeof(P9MsgHeader) + size); | |
| out_desc->chainWrite(0, (uint8_t *)&header_out, sizeof(header_out)); | |
| out_desc->chainWrite(sizeof(header_out), data, size); | |
| queue.produceDescriptor(main_desc, sizeof(P9MsgHeader) + size); | |
| kick(); | |
| } | |
| void | |
| VirtIO9PBase::dumpMsg(const P9MsgHeader &header, const uint8_t *data, size_t size) | |
| { | |
| #ifndef NDEBUG | |
| if (!DTRACE(VIO9P)) | |
| return; | |
| const P9MsgInfoMap::const_iterator it_msg(p9_msg_info.find(header.type)); | |
| if (it_msg != p9_msg_info.cend()) { | |
| const P9MsgInfo &info(it_msg->second); | |
| DPRINTF(VIO9P, "P9Msg[len = %i, type = %s (%i), tag = %i]\n", | |
| header.len, info.name, header.type, header.tag); | |
| } else { | |
| DPRINTF(VIO9P, "P9Msg[len = %i, type = Unknown (%i), tag = %i]\n", | |
| header.len, header.type, header.tag); | |
| } | |
| DDUMP(VIO9PData, data, size); | |
| #endif | |
| } | |
| VirtIO9PProxy::VirtIO9PProxy(Params *params) | |
| : VirtIO9PBase(params), deviceUsed(false) | |
| { | |
| } | |
| VirtIO9PProxy::~VirtIO9PProxy() | |
| { | |
| } | |
| void | |
| VirtIO9PProxy::serialize(CheckpointOut &cp) const | |
| { | |
| if (deviceUsed) { | |
| warn("Serializing VirtIO9Base device after device has been used. It is " | |
| "likely that state will be lost, and that the device will cease " | |
| "to work!"); | |
| } | |
| SERIALIZE_SCALAR(deviceUsed); | |
| VirtIO9PBase::serialize(cp); | |
| } | |
| void | |
| VirtIO9PProxy::unserialize(CheckpointIn &cp) | |
| { | |
| UNSERIALIZE_SCALAR(deviceUsed); | |
| if (deviceUsed) { | |
| warn("Unserializing VirtIO9Base device after device has been used. It is " | |
| "likely that state has been lost, and that the device will cease " | |
| "to work!"); | |
| } | |
| VirtIO9PBase::unserialize(cp); | |
| } | |
| void | |
| VirtIO9PProxy::recvTMsg(const P9MsgHeader &header, | |
| const uint8_t *data, size_t size) | |
| { | |
| deviceUsed = true; | |
| assert(header.len == sizeof(header) + size); | |
| // While technically not needed, we send the packet as one | |
| // contiguous segment to make some packet dissectors happy. | |
| uint8_t out[header.len]; | |
| P9MsgHeader header_out(htop9(header)); | |
| memcpy(out, (uint8_t *)&header_out, sizeof(header_out)); | |
| memcpy(out + sizeof(header_out), data, size); | |
| writeAll(out, sizeof(header_out) + size); | |
| } | |
| void | |
| VirtIO9PProxy::serverDataReady() | |
| { | |
| P9MsgHeader header; | |
| readAll((uint8_t *)&header, sizeof(header)); | |
| header = p9toh(header); | |
| const ssize_t payload_len(header.len - sizeof(header)); | |
| if (payload_len < 0) | |
| panic("Payload length is negative!\n"); | |
| uint8_t data[payload_len]; | |
| readAll(data, payload_len); | |
| sendRMsg(header, data, payload_len); | |
| } | |
| void | |
| VirtIO9PProxy::readAll(uint8_t *data, size_t len) | |
| { | |
| while (len) { | |
| ssize_t ret; | |
| while ((ret = read(data, len)) == -EAGAIN) | |
| ; | |
| if (ret < 0) | |
| panic("readAll: Read failed: %i\n", -ret); | |
| len -= ret; | |
| data += ret; | |
| } | |
| } | |
| void | |
| VirtIO9PProxy::writeAll(const uint8_t *data, size_t len) | |
| { | |
| while (len) { | |
| ssize_t ret; | |
| while ((ret = write(data, len)) == -EAGAIN) | |
| ; | |
| if (ret < 0) | |
| panic("writeAll: write failed: %i\n", -ret); | |
| len -= ret; | |
| data += ret; | |
| } | |
| } | |
| VirtIO9PDiod::VirtIO9PDiod(Params *params) | |
| : VirtIO9PProxy(params), | |
| fd_to_diod(-1), fd_from_diod(-1), diod_pid(-1) | |
| { | |
| // Register an exit callback so we can kill the diod process | |
| Callback* cb = new MakeCallback<VirtIO9PDiod, | |
| &VirtIO9PDiod::terminateDiod>(this); | |
| registerExitCallback(cb); | |
| } | |
| VirtIO9PDiod::~VirtIO9PDiod() | |
| { | |
| } | |
| void | |
| VirtIO9PDiod::startup() | |
| { | |
| startDiod(); | |
| dataEvent.reset(new DiodDataEvent(*this, fd_from_diod, POLLIN)); | |
| pollQueue.schedule(dataEvent.get()); | |
| } | |
| void | |
| VirtIO9PDiod::startDiod() | |
| { | |
| const Params *p(dynamic_cast<const Params *>(params())); | |
| int pipe_rfd[2]; | |
| int pipe_wfd[2]; | |
| const int DIOD_RFD = 3; | |
| const int DIOD_WFD = 4; | |
| const char *diod(p->diod.c_str()); | |
| DPRINTF(VIO9P, "Using diod at %s \n", p->diod.c_str()); | |
| if (pipe(pipe_rfd) == -1 || pipe(pipe_wfd) == -1) | |
| panic("Failed to create DIOD pipes: %i\n", errno); | |
| fd_to_diod = pipe_rfd[1]; | |
| fd_from_diod = pipe_wfd[0]; | |
| // Create Unix domain socket | |
| int socket_id = socket(AF_UNIX, SOCK_STREAM, 0); | |
| if (socket_id == -1) { | |
| panic("Socket creation failed %i \n", errno); | |
| } | |
| // Bind the socket to a path which will not be read | |
| struct sockaddr_un socket_address; | |
| memset(&socket_address, 0, sizeof(struct sockaddr_un)); | |
| socket_address.sun_family = AF_UNIX; | |
| const std::string socket_path = simout.resolve(p->socketPath); | |
| fatal_if(!OutputDirectory::isAbsolute(socket_path), "Please make the" \ | |
| " output directory an absolute path, else diod will fail!\n"); | |
| // Prevent overflow in strcpy | |
| fatal_if(sizeof(socket_address.sun_path) <= socket_path.length(), | |
| "Incorrect length of socket path"); | |
| strncpy(socket_address.sun_path, socket_path.c_str(), | |
| sizeof(socket_address.sun_path) - 1); | |
| if (bind(socket_id, (struct sockaddr*) &socket_address, | |
| sizeof(struct sockaddr_un)) == -1){ | |
| perror("Socket binding"); | |
| panic("Socket binding to %i failed - most likely the output dir" \ | |
| " and hence unused socket already exists \n", socket_id); | |
| } | |
| diod_pid = fork(); | |
| if (diod_pid == -1) { | |
| panic("Fork failed: %i\n", errno); | |
| } else if (diod_pid == 0) { | |
| // Create the socket which will later by used by the diod process | |
| close(STDIN_FILENO); | |
| if (dup2(pipe_rfd[0], DIOD_RFD) == -1 || | |
| dup2(pipe_wfd[1], DIOD_WFD) == -1) { | |
| panic("Failed to setup read/write pipes: %i\n", | |
| errno); | |
| } | |
| // Start diod | |
| execlp(diod, diod, | |
| "-f", // start in foreground | |
| "-r", "3", // setup read FD | |
| "-w", "4", // setup write FD | |
| "-e", p->root.c_str(), // path to export | |
| "-n", // disable security | |
| "-S", // squash all users | |
| "-l", socket_path.c_str(), // pass the socket | |
| (char *)NULL); | |
| perror("Starting DIOD"); | |
| panic("Failed to execute diod to %s: %i\n",socket_path, errno); | |
| } else { | |
| close(pipe_rfd[0]); | |
| close(pipe_wfd[1]); | |
| inform("Started diod with PID %u, you might need to manually kill " \ | |
| " diod if gem5 crashes \n", diod_pid); | |
| } | |
| #undef DIOD_RFD | |
| #undef DIOD_WFD | |
| } | |
| ssize_t | |
| VirtIO9PDiod::read(uint8_t *data, size_t len) | |
| { | |
| assert(fd_from_diod != -1); | |
| const int ret(::read(fd_from_diod, (void *)data, len)); | |
| return ret < 0 ? -errno : ret; | |
| } | |
| ssize_t | |
| VirtIO9PDiod::write(const uint8_t *data, size_t len) | |
| { | |
| assert(fd_to_diod != -1); | |
| const int ret(::write(fd_to_diod, (const void *)data, len)); | |
| return ret < 0 ? -errno : ret; | |
| } | |
| void | |
| VirtIO9PDiod::DiodDataEvent::process(int revent) | |
| { | |
| parent.serverDataReady(); | |
| } | |
| void | |
| VirtIO9PDiod::terminateDiod() | |
| { | |
| assert(diod_pid != -1); | |
| DPRINTF(VIO9P, "Trying to kill diod at pid %u \n", diod_pid); | |
| if (kill(diod_pid, SIGTERM) != 0) { | |
| perror("Killing diod process"); | |
| warn("Failed to kill diod using SIGTERM"); | |
| return; | |
| } | |
| // Check if kill worked | |
| for (unsigned i = 0; i < 5; i++) { | |
| int wait_return = waitpid(diod_pid, NULL, WNOHANG); | |
| if (wait_return == diod_pid) { | |
| // Managed to kill diod | |
| return; | |
| } else if (wait_return == 0) { | |
| // Diod is not killed so sleep and try again | |
| usleep(500); | |
| } else { | |
| // Failed in waitpid | |
| perror("Waitpid"); | |
| warn("Failed in waitpid"); | |
| } | |
| } | |
| // Try again to kill diod with sigkill | |
| inform("Trying to kill diod with SIGKILL as SIGTERM failed \n"); | |
| if (kill(diod_pid, SIGKILL) != 0) { | |
| perror("Killing diod process"); | |
| warn("Failed to kill diod using SIGKILL"); | |
| } else { | |
| // Managed to kill diod | |
| return; | |
| } | |
| } | |
| VirtIO9PDiod * | |
| VirtIO9PDiodParams::create() | |
| { | |
| return new VirtIO9PDiod(this); | |
| } | |
| VirtIO9PSocket::VirtIO9PSocket(Params *params) | |
| : VirtIO9PProxy(params), fdSocket(-1) | |
| { | |
| } | |
| VirtIO9PSocket::~VirtIO9PSocket() | |
| { | |
| } | |
| void | |
| VirtIO9PSocket::startup() | |
| { | |
| connectSocket(); | |
| dataEvent.reset(new SocketDataEvent(*this, fdSocket, POLLIN)); | |
| pollQueue.schedule(dataEvent.get()); | |
| } | |
| void | |
| VirtIO9PSocket::connectSocket() | |
| { | |
| const Params &p(dynamic_cast<const Params &>(*params())); | |
| int ret; | |
| struct addrinfo hints, *result; | |
| memset(&hints, 0, sizeof(hints)); | |
| hints.ai_family = AF_UNSPEC; | |
| hints.ai_socktype = SOCK_STREAM; | |
| hints.ai_flags = 0; | |
| hints.ai_protocol = 0; | |
| if ((ret = getaddrinfo(p.server.c_str(), p.port.c_str(), | |
| &hints, &result)) != 0) | |
| panic("getaddrinfo: %s\n", gai_strerror(ret)); | |
| DPRINTF(VIO9P, "Connecting to 9p server '%s'.\n", p.server); | |
| for (struct addrinfo *rp = result; rp; rp = rp->ai_next) { | |
| fdSocket = socket(rp->ai_family, rp->ai_socktype, | |
| rp->ai_protocol); | |
| if (fdSocket == -1) { | |
| continue; | |
| } else if (connect(fdSocket, rp->ai_addr, rp->ai_addrlen) != -1) { | |
| break; | |
| } else { | |
| close(fdSocket); | |
| fdSocket = -1; | |
| } | |
| } | |
| freeaddrinfo(result); | |
| if (fdSocket == -1) | |
| panic("Failed to connect to 9p server (%s:%s)", p.server, p.port); | |
| } | |
| void | |
| VirtIO9PSocket::socketDisconnect() | |
| { | |
| panic("9P Socket disconnected!\n"); | |
| } | |
| ssize_t | |
| VirtIO9PSocket::read(uint8_t *data, size_t len) | |
| { | |
| assert(fdSocket != -1); | |
| int ret; | |
| ret = ::recv(fdSocket, (void *)data, len, 0); | |
| if (ret == 0) | |
| socketDisconnect(); | |
| return ret < 0 ? -errno : ret; | |
| } | |
| ssize_t | |
| VirtIO9PSocket::write(const uint8_t *data, size_t len) | |
| { | |
| assert(fdSocket != -1); | |
| int ret(::send(fdSocket, (const void *)data, len, 0)); | |
| return ret < 0 ? -errno : ret; | |
| } | |
| void | |
| VirtIO9PSocket::SocketDataEvent::process(int revent) | |
| { | |
| parent.serverDataReady(); | |
| } | |
| VirtIO9PSocket * | |
| VirtIO9PSocketParams::create() | |
| { | |
| return new VirtIO9PSocket(this); | |
| } |