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