// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This utility allows to read a file from the local file system through a // network protocol. The protocol is the following request response protocol: // // Opening a new file: // Request: O path_to_file\n // Success: O // Error: E // // Seeking on the last opened file: // Request: S offset whence\n // Success: 0 // Error: E // // Reading the last opened file at the current position: // Request: R size\n // Success: 0{size encoded on 4 bytes in network order}{content} // Error: E namespace { // Display an error message and exit. void error(const char* msg) { perror(msg); exit(1); } // Keep track of an active connection with a client. class Connection { public: // Takes ownership of the given fd which is an opened socket to the client. explicit Connection(int fd) : fd_(fd), file_fd_(-1), buffer_size_(0), buffer_(nullptr, free) {} ~Connection() { Close(); } int fd() { return fd_; }; void Close() { if (fd_ != -1) { close(fd_); fd_ = -1; } if (file_fd_ != -1) { close(file_fd_); file_fd_ = -1; } } // Returns whether this connection has some data to write to the client. bool NeedsWriting() { return write_buffer_.size() > 0; } // Try to write buffered data to the client. This should only be called when // the socket is writeable, otherwise this method may block. bool Write() { int nb_written = write(fd_, write_buffer_.data(), write_buffer_.size()); if (nb_written <= 0) { Close(); return false; } write_buffer_ = std::string(write_buffer_, nb_written); return true; } // Read any data available from the client, and send any responses if a full // request has been read. This should only be called when the socket is // readable, otherwise this method may block. bool Read() { char buffer[4096]; int nb_read = read(fd_, buffer, sizeof(buffer)); if (nb_read <= 0) { Close(); return false; } read_buffer_ += std::string(buffer, nb_read); Respond(); return true; } private: // Success message. const char kSuccess = 'O'; // Error message. const char kError = 'E'; // Read any available request in the read buffer and write the responses in // the write buffer. void Respond() { std::string::size_type eol_position = read_buffer_.find('\n'); while (eol_position != std::string::npos) { std::string command(read_buffer_, 0, eol_position); read_buffer_ = std::string(read_buffer_, eol_position + 1); std::string argument(command, 2); switch (command[0]) { case 'O': OpenCommand(argument); break; case 'S': SeekCommand(argument); break; case 'R': ReadCommand(argument); break; default: write_buffer_ += kError; } eol_position = read_buffer_.find('\n'); } } // Handle the open command. void OpenCommand(const std::string& file) { if (file_fd_ != -1) { close(file_fd_); file_fd_ = -1; } file_fd_ = open(file.c_str(), O_RDONLY); if (file_fd_ == -1) { perror("Unable to open file"); write_buffer_ += kError; return; } write_buffer_ += kSuccess; } // Handle the seek command. void SeekCommand(const std::string& parameters) { if (file_fd_ == -1) { write_buffer_ += kError; return; } int32_t offset; int32_t whence; if (sscanf(parameters.c_str(), "%" SCNd32 " %" SCNd32, &offset, &whence) != 2) { write_buffer_ += kError; return; } if (lseek(file_fd_, offset, whence) == -1) { perror("Unable to seek"); write_buffer_ += kError; return; } write_buffer_ += kSuccess; } // Handle the read command. void ReadCommand(const std::string& parameters) { if (file_fd_ == -1) { write_buffer_ += kError; return; } int32_t size; if (sscanf(parameters.c_str(), "%" SCNd32, &size) != 1) { write_buffer_ += kError; return; } if (size <= 0) { write_buffer_ += kError; return; } EnsureBufferCapacity(size); int32_t result = read(file_fd_, buffer_.get(), size); if (result < 0) { perror("Unable to read"); close(file_fd_); file_fd_ = -1; write_buffer_ += kError; return; } write_buffer_ += kSuccess; int32_t size_to_send = htonl(result); static_assert(sizeof(size_to_send) == 4, "Must send size with 4 byte."); write_buffer_ += std::string(reinterpret_cast(&size_to_send), 4); write_buffer_ += std::string(buffer_.get(), result); } // Ensure that |buffer_| has the capacity needed to read from the local file. void EnsureBufferCapacity(size_t capacity) { if (buffer_size_ >= capacity) { return; } if (buffer_size_ == 0) { buffer_.reset(static_cast(malloc(capacity))); buffer_size_ = capacity; return; } buffer_.reset(static_cast(realloc(buffer_.release(), capacity))); buffer_size_ = capacity; } // File descriptor of the socket connected to the client. int fd_; // File descriptor of the current read file. int file_fd_; size_t buffer_size_; std::unique_ptr buffer_; std::string write_buffer_; std::string read_buffer_; }; } // namespace int main(int argc, char** argv) { // The port to bind to. int port = 0; if (argc > 1) { port = atoi(argv[1]); } // Setup the server socket. int server_socket, connected_socket; struct sockaddr_in server_address, client_address; server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket < 0) { error("Unable to open socket"); } bzero(&server_address, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = INADDR_ANY; server_address.sin_port = port; if (bind(server_socket, reinterpret_cast(&server_address), sizeof(server_address)) < 0) { error("Unable to bind socket"); } socklen_t socket_size = sizeof(server_address); if (getsockname(server_socket, reinterpret_cast(&server_address), &socket_size) < 0) { perror("Unable to retrieve socket information"); } // Print the port on the bound port on the standard output. printf("%d\n", ntohs(server_address.sin_port)); // Start listening for client. listen(server_socket, 5); // Wait for the first client to connect. socket_size = sizeof(client_address); connected_socket = accept(server_socket, reinterpret_cast(&client_address), &socket_size); if (connected_socket < 0) { error("Unable to accept the intiial client"); } // Keep track of active connections. std::vector> connections; connections.push_back( std::unique_ptr(new Connection(connected_socket))); // Stop when all clients have disconnected. while (connections.size()) { // Prepate data to wait for any I/O to be ready. fd_set rset; fd_set wset; FD_ZERO(&rset); FD_ZERO(&wset); // Always wait for a new connection. FD_SET(server_socket, &rset); int max_fd = server_socket + 1; for (auto& c : connections) { // Always wait for a client sending data. FD_SET(c->fd(), &rset); // Wait for a client to be ready to receive data only if some data needs // to be sent. if (c->NeedsWriting()) { FD_SET(c->fd(), &wset); } max_fd = std::max(max_fd, c->fd() + 1); } // Wait for any I/O to be ready. select(max_fd, &rset, &wset, nullptr, nullptr); // Check if a client a connected. if (FD_ISSET(server_socket, &rset)) { int fd = accept(server_socket, reinterpret_cast(&client_address), &socket_size); if (fd >= 0) { connections.push_back(std::unique_ptr(new Connection(fd))); } } // Check each connection. for (auto& c : connections) { if (FD_ISSET(c->fd(), &rset)) { if (!c->Read()) { // If a fatal error happen, delete the connection. c.reset(); } } if (c && FD_ISSET(c->fd(), &wset)) { if (!c->Write()) { // If a fatal error happen, delete the connection. c.reset(); } } } // Remove any deleted connection from the list of active connections. connections.erase( std::remove_if(connections.begin(), connections.end(), [](const std::unique_ptr& c) { return !c; }), connections.end()); } return 0; }