blob: c12132ae0d4f4628b3a7c992694db55ccccbbf02 [file] [log] [blame]
// 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 <fcntl.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
// 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<char*>(&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<char*>(malloc(capacity)));
buffer_size_ = capacity;
return;
}
buffer_.reset(static_cast<char*>(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<char, decltype(free)*> 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<struct sockaddr*>(&server_address),
sizeof(server_address)) < 0) {
error("Unable to bind socket");
}
socklen_t socket_size = sizeof(server_address);
if (getsockname(server_socket,
reinterpret_cast<struct sockaddr*>(&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<struct sockaddr*>(&client_address),
&socket_size);
if (connected_socket < 0) {
error("Unable to accept the intiial client");
}
// Keep track of active connections.
std::vector<std::unique_ptr<Connection>> connections;
connections.push_back(
std::unique_ptr<Connection>(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<struct sockaddr*>(&client_address),
&socket_size);
if (fd >= 0) {
connections.push_back(std::unique_ptr<Connection>(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<Connection>& c) { return !c; }),
connections.end());
}
return 0;
}