/* * Copyright 2005, Ingo Weinhold . * All rights reserved. Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include static const bigtime_t kRequestTimeout = 100000LL; using std::nothrow; #if __BYTE_ORDER == __LITTLE_ENDIAN static inline uint64_t swap_uint64(uint64_t data) { return ((data & 0xff) << 56) | ((data & 0xff00) << 40) | ((data & 0xff0000) << 24) | ((data & 0xff000000) << 8) | ((data >> 8) & 0xff000000) | ((data >> 24) & 0xff0000) | ((data >> 40) & 0xff00) | ((data >> 56) & 0xff); } #define host_to_net64(data) swap_uint64(data) #define net_to_host64(data) swap_uint64(data) #endif #if __BYTE_ORDER == __BIG_ENDIAN #define host_to_net64(data) (data) #define net_to_host64(data) (data) #endif #undef htonll #undef ntohll #define htonll(data) host_to_net64(data) #define ntohll(data) net_to_host64(data) // constructor RemoteDisk::RemoteDisk() : fServerAddress(INADDR_ANY), fServerPort(0), fImageSize(0), fRequestID(0), fSocket(NULL), fPacket(NULL) { } // destructor RemoteDisk::~RemoteDisk() { delete fSocket; delete fPacket; } // Init status_t RemoteDisk::Init(ip_addr_t serverAddress, uint16 serverPort, off_t imageSize) { fServerAddress = serverAddress; fServerPort = serverPort; fImageSize = imageSize; // create and bind socket fSocket = new(nothrow) UDPSocket; if (!fSocket) return B_NO_MEMORY; status_t error = fSocket->Bind(INADDR_ANY, 6666); if (error != B_OK) return error; return B_OK; } // ReadAt ssize_t RemoteDisk::ReadAt(void */*cookie*/, off_t pos, void *_buffer, size_t bufferSize) { if (!fSocket) return B_NO_INIT; uint8 *buffer = (uint8*)_buffer; if (!buffer || pos < 0) return B_BAD_VALUE; if (bufferSize == 0) return 0; // Check whether the current packet already contains the beginning of the // data to read. ssize_t bytesRead = _ReadFromPacket(pos, buffer, bufferSize); // If there still remains something to be read, we need to get it from the // server. status_t error = B_OK; while (bufferSize > 0) { // prepare request remote_disk_header request; request.offset = htonll(pos); uint32 toRead = min_c(bufferSize, REMOTE_DISK_BLOCK_SIZE); request.size = htons(toRead); request.command = REMOTE_DISK_READ_REQUEST; // send request UDPPacket *packet; error = _SendRequest(&request, sizeof(request), REMOTE_DISK_READ_REPLY, &packet); if (error != B_OK) break; // check for errors int16 packetSize = ntohs(((remote_disk_header*)packet->Data())->size); if (packetSize < 0) { if (packetSize == REMOTE_DISK_IO_ERROR) error = B_IO_ERROR; else if (packetSize == REMOTE_DISK_BAD_REQUEST) error = B_BAD_VALUE; break; } // make the reply packet the current packet delete fPacket; fPacket = packet; // read from the packet size_t packetBytesRead = _ReadFromPacket(pos, buffer, bufferSize); if (packetBytesRead == 0) break; bytesRead += packetBytesRead; } // only return an error, when we were not able to read anything at all return (bytesRead == 0 ? error : bytesRead); } // WriteAt ssize_t RemoteDisk::WriteAt(void */*cookie*/, off_t pos, const void *buffer, size_t bufferSize) { // Not needed in the boot loader. return B_PERMISSION_DENIED; } // GetName status_t RemoteDisk::GetName(char *nameBuffer, size_t bufferSize) const { if (!nameBuffer) return B_BAD_VALUE; snprintf(nameBuffer, bufferSize, "RemoteDisk:%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ":%hd", (fServerAddress >> 24) & 0xff, (fServerAddress >> 16) & 0xff, (fServerAddress >> 8) & 0xff, fServerAddress & 0xff, fServerPort); return B_OK; } // Size off_t RemoteDisk::Size() const { return fImageSize; } ip_addr_t RemoteDisk::ServerIPAddress() const { return fServerAddress; } uint16 RemoteDisk::ServerPort() const { return fServerPort; } // FindAnyRemoteDisk RemoteDisk * RemoteDisk::FindAnyRemoteDisk() { // create a socket and bind it UDPSocket socket; status_t error = socket.Bind(INADDR_ANY, 6665); if (error != B_OK) { printf("RemoteDisk::GetAnyRemoteDisk(): Failed to bind socket.\n"); return NULL; } // prepare request remote_disk_header request; request.command = REMOTE_DISK_HELLO_REQUEST; // send request UDPPacket *packet; error = _SendRequest(&socket, INADDR_BROADCAST, REMOTE_DISK_SERVER_PORT, &request, sizeof(request), REMOTE_DISK_HELLO_REPLY, &packet); if (error != B_OK) { printf("RemoteDisk::GetAnyRemoteDisk(): Got no server reply.\n"); return NULL; } remote_disk_header *reply = (remote_disk_header*)packet->Data(); // create a RemoteDisk object RemoteDisk *remoteDisk = new(nothrow) RemoteDisk; if (remoteDisk) { error = remoteDisk->Init(packet->SourceAddress(), ntohs(reply->port), ntohll(reply->offset)); if (error != B_OK) { delete remoteDisk; remoteDisk = NULL; } } delete packet; return remoteDisk; } // _ReadFromPacket ssize_t RemoteDisk::_ReadFromPacket(off_t &pos, uint8 *&buffer, size_t &bufferSize) { if (!fPacket) return 0; remote_disk_header *header = (remote_disk_header*)fPacket->Data(); uint64 packetOffset = ntohll(header->offset); uint32 packetSize = ntohs(header->size); if (packetOffset > (uint64)pos || packetOffset + packetSize <= (uint64)pos) return 0; // we do indeed have some bytes already size_t toCopy = size_t(packetOffset + packetSize - (uint64)pos); if (toCopy > bufferSize) toCopy = bufferSize; memcpy(buffer, header->data + (pos - packetOffset), toCopy); pos += toCopy; buffer += toCopy; bufferSize -= toCopy; return toCopy; } // _SendRequest status_t RemoteDisk::_SendRequest(UDPSocket *socket, ip_addr_t serverAddress, uint16 serverPort, remote_disk_header *request, size_t size, uint8 expectedReply, UDPPacket **_packet) { request->port = htons(socket->Port()); // try sending the request 3 times at most for (int i = 0; i < 3; i++) { // send request status_t error = socket->Send(serverAddress, serverPort, request, size); if (error != B_OK) return error; // receive reply bigtime_t timeout = system_time() + kRequestTimeout; do { UDPPacket *packet; error = socket->Receive(&packet, timeout - system_time()); if (error == B_OK) { // got something; check, if it is looks good if (packet->DataSize() >= sizeof(remote_disk_header)) { remote_disk_header *reply = (remote_disk_header*)packet->Data(); if (reply->request_id == request->request_id && reply->command == expectedReply) { *_packet = packet; return B_OK; } } // reply not OK delete packet; } else if (error != B_TIMED_OUT && error != B_WOULD_BLOCK) return error; } while (timeout > system_time()); } // no reply return B_ERROR; } // _SendRequest status_t RemoteDisk::_SendRequest(remote_disk_header *request, size_t size, uint8 expectedReply, UDPPacket **packet) { request->request_id = fRequestID++; return _SendRequest(fSocket, fServerAddress, fServerPort, request, size, expectedReply, packet); }