xref: /haiku/src/system/boot/loader/net/RemoteDisk.cpp (revision b247f935d133a42c427cad8a759a1bf2f65bc290)
1 /*
2  * Copyright 2005, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include <boot/net/RemoteDisk.h>
7 
8 #include <new>
9 
10 #include <endian.h>
11 #include <stdio.h>
12 
13 #include <OS.h>
14 #include <SupportDefs.h>
15 
16 #include <boot/net/UDP.h>
17 
18 
19 static const bigtime_t kRequestTimeout = 100000LL;
20 
21 
22 using std::nothrow;
23 
24 
25 #if __BYTE_ORDER == __LITTLE_ENDIAN
26 
27 static inline
28 uint64_t swap_uint64(uint64_t data)
29 {
30 	return ((data & 0xff) << 56)
31 		| ((data & 0xff00) << 40)
32 		| ((data & 0xff0000) << 24)
33 		| ((data & 0xff000000) << 8)
34 		| ((data >> 8) & 0xff000000)
35 		| ((data >> 24) & 0xff0000)
36 		| ((data >> 40) & 0xff00)
37 		| ((data >> 56) & 0xff);
38 }
39 
40 #define host_to_net64(data)	swap_uint64(data)
41 #define net_to_host64(data)	swap_uint64(data)
42 
43 #endif
44 
45 #if __BYTE_ORDER == __BIG_ENDIAN
46 #define host_to_net64(data)	(data)
47 #define net_to_host64(data)	(data)
48 #endif
49 
50 #undef htonll
51 #undef ntohll
52 #define htonll(data)	host_to_net64(data)
53 #define ntohll(data)	net_to_host64(data)
54 
55 
56 // constructor
57 RemoteDisk::RemoteDisk()
58 	: fServerAddress(INADDR_ANY),
59 	  fServerPort(0),
60 	  fImageSize(0),
61 	  fRequestID(0),
62 	  fSocket(NULL),
63 	  fPacket(NULL)
64 {
65 }
66 
67 // destructor
68 RemoteDisk::~RemoteDisk()
69 {
70 	delete fSocket;
71 	delete fPacket;
72 }
73 
74 // Init
75 status_t
76 RemoteDisk::Init(ip_addr_t serverAddress, uint16 serverPort, off_t imageSize)
77 {
78 	fServerAddress = serverAddress;
79 	fServerPort = serverPort;
80 	fImageSize = imageSize;
81 
82 	// create and bind socket
83 	fSocket = new(nothrow) UDPSocket;
84 	if (!fSocket)
85 		return B_NO_MEMORY;
86 
87 	status_t error = fSocket->Bind(INADDR_ANY, 6666);
88 	if (error != B_OK)
89 		return error;
90 
91 	return B_OK;
92 }
93 
94 // ReadAt
95 ssize_t
96 RemoteDisk::ReadAt(void */*cookie*/, off_t pos, void *_buffer,
97 	size_t bufferSize)
98 {
99 	if (!fSocket)
100 		return B_NO_INIT;
101 
102 	uint8 *buffer = (uint8*)_buffer;
103 	if (!buffer || pos < 0)
104 		return B_BAD_VALUE;
105 
106 	if (bufferSize == 0)
107 		return 0;
108 
109 	// Check whether the current packet already contains the beginning of the
110 	// data to read.
111 	ssize_t bytesRead = _ReadFromPacket(pos, buffer, bufferSize);
112 
113 	// If there still remains something to be read, we need to get it from the
114 	// server.
115 	status_t error = B_OK;
116 	while (bufferSize > 0) {
117 		// prepare request
118 		remote_disk_header request;
119 		request.offset = htonll(pos);
120 		uint32 toRead = min_c(bufferSize, REMOTE_DISK_BLOCK_SIZE);
121 		request.size = htons(toRead);
122 		request.command = REMOTE_DISK_READ_REQUEST;
123 
124 		// send request
125 		UDPPacket *packet;
126 		error = _SendRequest(&request, sizeof(request), REMOTE_DISK_READ_REPLY,
127 			&packet);
128 		if (error != B_OK)
129 			break;
130 
131 		// check for errors
132 		int16 packetSize = ntohs(((remote_disk_header*)packet->Data())->size);
133 		if (packetSize < 0) {
134 			if (packetSize == REMOTE_DISK_IO_ERROR)
135 				error = B_IO_ERROR;
136 			else if (packetSize == REMOTE_DISK_BAD_REQUEST)
137 				error = B_BAD_VALUE;
138 			break;
139 		}
140 
141 		// make the reply packet the current packet
142 		delete fPacket;
143 		fPacket = packet;
144 
145 		// read from the packet
146 		size_t packetBytesRead = _ReadFromPacket(pos, buffer, bufferSize);
147 		if (packetBytesRead == 0)
148 			break;
149 		bytesRead += packetBytesRead;
150 	}
151 
152 	// only return an error, when we were not able to read anything at all
153 	return (bytesRead == 0 ? error : bytesRead);
154 }
155 
156 // WriteAt
157 ssize_t
158 RemoteDisk::WriteAt(void */*cookie*/, off_t pos, const void *buffer,
159 	size_t bufferSize)
160 {
161 	// Not needed in the boot loader.
162 	return B_PERMISSION_DENIED;
163 }
164 
165 // GetName
166 status_t
167 RemoteDisk::GetName(char *nameBuffer, size_t bufferSize) const
168 {
169 	if (!nameBuffer)
170 		return B_BAD_VALUE;
171 
172 	snprintf(nameBuffer, bufferSize,
173 		"RemoteDisk:%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ":%hd",
174 		(fServerAddress >> 24) & 0xff, (fServerAddress >> 16) & 0xff,
175 		(fServerAddress >> 8) & 0xff, fServerAddress & 0xff, fServerPort);
176 
177 	return B_OK;
178 }
179 
180 // Size
181 off_t
182 RemoteDisk::Size() const
183 {
184 	return fImageSize;
185 }
186 
187 ip_addr_t
188 RemoteDisk::ServerIPAddress() const
189 {
190 	return fServerAddress;
191 }
192 
193 uint16
194 RemoteDisk::ServerPort() const
195 {
196 	return fServerPort;
197 }
198 
199 // FindAnyRemoteDisk
200 RemoteDisk *
201 RemoteDisk::FindAnyRemoteDisk()
202 {
203 	// create a socket and bind it
204 	UDPSocket socket;
205 	status_t error = socket.Bind(INADDR_ANY, 6665);
206 	if (error != B_OK) {
207 		printf("RemoteDisk::GetAnyRemoteDisk(): Failed to bind socket.\n");
208 		return NULL;
209 	}
210 
211 	// prepare request
212 	remote_disk_header request;
213 	request.command = REMOTE_DISK_HELLO_REQUEST;
214 
215 	// send request
216 	UDPPacket *packet;
217 	error = _SendRequest(&socket, INADDR_BROADCAST, REMOTE_DISK_SERVER_PORT,
218 		&request, sizeof(request), REMOTE_DISK_HELLO_REPLY, &packet);
219 	if (error != B_OK) {
220 		printf("RemoteDisk::GetAnyRemoteDisk(): Got no server reply.\n");
221 		return NULL;
222 	}
223 	remote_disk_header *reply = (remote_disk_header*)packet->Data();
224 
225 	// create a RemoteDisk object
226 	RemoteDisk *remoteDisk = new(nothrow) RemoteDisk;
227 	if (remoteDisk) {
228 		error = remoteDisk->Init(packet->SourceAddress(), ntohs(reply->port),
229 			ntohll(reply->offset));
230 		if (error != B_OK) {
231 			delete remoteDisk;
232 			remoteDisk = NULL;
233 		}
234 	}
235 
236 	delete packet;
237 
238 	return remoteDisk;
239 }
240 
241 // _ReadFromPacket
242 ssize_t
243 RemoteDisk::_ReadFromPacket(off_t &pos, uint8 *&buffer, size_t &bufferSize)
244 {
245 	if (!fPacket)
246 		return 0;
247 
248 	remote_disk_header *header = (remote_disk_header*)fPacket->Data();
249 	uint64 packetOffset = ntohll(header->offset);
250 	uint32 packetSize = ntohs(header->size);
251 	if (packetOffset > (uint64)pos || packetOffset + packetSize <= (uint64)pos)
252 		return 0;
253 
254 	// we do indeed have some bytes already
255 	size_t toCopy = size_t(packetOffset + packetSize - (uint64)pos);
256 	if (toCopy > bufferSize)
257 		toCopy = bufferSize;
258 	memcpy(buffer, header->data + (pos - packetOffset), toCopy);
259 
260 	pos += toCopy;
261 	buffer += toCopy;
262 	bufferSize -= toCopy;
263 	return toCopy;
264 }
265 
266 // _SendRequest
267 status_t
268 RemoteDisk::_SendRequest(UDPSocket *socket, ip_addr_t serverAddress,
269 	uint16 serverPort, remote_disk_header *request, size_t size,
270 	uint8 expectedReply, UDPPacket **_packet)
271 {
272 	request->port = htons(socket->Port());
273 
274 	// try sending the request 3 times at most
275 	for (int i = 0; i < 3; i++) {
276 		// send request
277 		status_t error = socket->Send(serverAddress, serverPort, request, size);
278 		if (error != B_OK)
279 			return error;
280 
281 		// receive reply
282 		bigtime_t timeout = system_time() + kRequestTimeout;
283 		do {
284 			UDPPacket *packet;
285 			error = socket->Receive(&packet, timeout - system_time());
286 			if (error == B_OK) {
287 				// got something; check, if it is looks good
288 				if (packet->DataSize() >= sizeof(remote_disk_header)) {
289 					remote_disk_header *reply
290 						= (remote_disk_header*)packet->Data();
291 					if (reply->request_id == request->request_id
292 						&& reply->command == expectedReply) {
293 						*_packet = packet;
294 						return B_OK;
295 					}
296 				}
297 
298 				// reply not OK
299 				delete packet;
300 			} else if (error != B_TIMED_OUT && error != B_WOULD_BLOCK)
301 				return error;
302 
303 		} while (timeout > system_time());
304 	}
305 
306 	// no reply
307 	return B_ERROR;
308 }
309 
310 // _SendRequest
311 status_t
312 RemoteDisk::_SendRequest(remote_disk_header *request, size_t size,
313 	uint8 expectedReply, UDPPacket **packet)
314 {
315 	request->request_id = fRequestID++;
316 	return _SendRequest(fSocket, fServerAddress, fServerPort, request, size,
317 		expectedReply, packet);
318 }
319