xref: /haiku/src/add-ons/kernel/drivers/disk/virtual/remote_disk/RemoteDisk.cpp (revision 582da17386c4a192ca30270d6b0b95f561cf5843)
1 /*
2  * Copyright 2005-2007, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "RemoteDisk.h"
7 
8 #include <new>
9 
10 #include <endian.h>
11 #include <errno.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/socket.h>
16 #include <sys/time.h>
17 #include <unistd.h>
18 
19 #include <KernelExport.h>
20 #include <OS.h>
21 
22 #include <kernel.h>		// for IS_USER_ADDRESS()
23 
24 
25 //#define TRACE_REMOTE_DISK
26 #ifdef TRACE_REMOTE_DISK
27 #	define TRACE(x) dprintf x
28 #else
29 #	define TRACE(x) do {} while (false)
30 #endif
31 
32 
33 static const bigtime_t kReceiveTimeout = 2000000LL;
34 static const bigtime_t kRequestTimeout = 6000000LL;
35 
36 #if __BYTE_ORDER == __LITTLE_ENDIAN
37 
38 static inline
39 uint64_t swap_uint64(uint64_t data)
40 {
41 	return ((data & 0xff) << 56)
42 		| ((data & 0xff00) << 40)
43 		| ((data & 0xff0000) << 24)
44 		| ((data & 0xff000000) << 8)
45 		| ((data >> 8) & 0xff000000)
46 		| ((data >> 24) & 0xff0000)
47 		| ((data >> 40) & 0xff00)
48 		| ((data >> 56) & 0xff);
49 }
50 
51 #define host_to_net64(data)	swap_uint64(data)
52 #define net_to_host64(data)	swap_uint64(data)
53 
54 #endif
55 
56 #if __BYTE_ORDER == __BIG_ENDIAN
57 #define host_to_net64(data)	(data)
58 #define net_to_host64(data)	(data)
59 #endif
60 
61 #undef htonll
62 #undef ntohll
63 #define htonll(data)	host_to_net64(data)
64 #define ntohll(data)	net_to_host64(data)
65 
66 
67 enum {
68 	BUFFER_SIZE	= 2048
69 };
70 
71 
72 // constructor
73 RemoteDisk::RemoteDisk()
74 	: fImageSize(0),
75 	  fRequestID(0),
76 	  fSocket(-1),
77 	  fPacket(NULL),
78 	  fPacketSize(0)
79 {
80 }
81 
82 
83 // destructor
84 RemoteDisk::~RemoteDisk()
85 {
86 	if (fSocket >= 0)
87 		close(fSocket);
88 
89 	free(fPacket);
90 }
91 
92 
93 // Init
94 status_t
95 RemoteDisk::Init(uint32 serverAddress, uint16 serverPort, off_t imageSize)
96 {
97 	status_t error = _Init();
98 	if (error != B_OK)
99 		return error;
100 
101 	fServerAddress.sin_family = AF_INET;
102 	fServerAddress.sin_port = htons(serverPort);
103 	fServerAddress.sin_addr.s_addr = htonl(serverAddress);
104 	fServerAddress.sin_len = sizeof(sockaddr_in);
105 
106 	fImageSize = imageSize;
107 
108 	return B_OK;
109 }
110 
111 
112 // FindAnyRemoteDisk
113 status_t
114 RemoteDisk::FindAnyRemoteDisk()
115 {
116 	status_t error = _Init();
117 	if (error != B_OK)
118 		return error;
119 
120 	// prepare request
121 	remote_disk_header request;
122 	request.command = REMOTE_DISK_HELLO_REQUEST;
123 
124 	// init server address to broadcast
125 	fServerAddress.sin_family = AF_INET;
126 	fServerAddress.sin_port = htons(REMOTE_DISK_SERVER_PORT);
127 	fServerAddress.sin_addr.s_addr = htonl(INADDR_BROADCAST);
128 	fServerAddress.sin_len = sizeof(sockaddr_in);
129 
130 	// set SO_BROADCAST on socket
131 	int soBroadcastValue = 1;
132 	if (fSocketModule->setsockopt(fSocket, SOL_SOCKET, SO_BROADCAST,
133 		&soBroadcastValue, sizeof(soBroadcastValue)) < 0) {
134 		dprintf("RemoteDisk::Init(): Failed to set SO_BROADCAST on socket: "
135 			"%s\n", strerror(errno));
136 	}
137 
138 	// send request
139 	sockaddr_in serverAddress;
140 	error = _SendRequest(&request, sizeof(request), REMOTE_DISK_HELLO_REPLY,
141 		&serverAddress);
142 	if (error != B_OK) {
143 		dprintf("RemoteDisk::FindAnyRemoteDisk(): Got no server reply: %s\n",
144 			strerror(error));
145 		return error;
146 	}
147 	remote_disk_header* reply = (remote_disk_header*)fPacket;
148 
149 	// unset SO_BROADCAST on socket
150 	soBroadcastValue = 0;
151 	if (fSocketModule->setsockopt(fSocket, SOL_SOCKET, SO_BROADCAST,
152 		&soBroadcastValue, sizeof(soBroadcastValue)) < 0) {
153 		dprintf("RemoteDisk::Init(): Failed to unset SO_BROADCAST on socket: "
154 			"%s\n", strerror(errno));
155 	}
156 
157 	// init server address and size
158 	fServerAddress = serverAddress;
159 	fServerAddress.sin_port = reply->port;
160 
161 	fImageSize = ntohll(reply->offset);
162 
163 	return B_OK;
164 }
165 
166 
167 // ReadAt
168 ssize_t
169 RemoteDisk::ReadAt(off_t pos, void *_buffer, size_t bufferSize)
170 {
171 	if (fSocket < 0)
172 		return B_NO_INIT;
173 
174 	uint8 *buffer = (uint8*)_buffer;
175 	if (!buffer || pos < 0)
176 		return B_BAD_VALUE;
177 
178 	if (bufferSize == 0)
179 		return 0;
180 
181 	// Check whether the current packet already contains the beginning of the
182 	// data to read.
183 	ssize_t bytesRead = _ReadFromPacket(pos, buffer, bufferSize);
184 	if (bytesRead < 0)
185 		return bytesRead;
186 
187 	// If there still remains something to be read, we need to get it from the
188 	// server.
189 	status_t error = B_OK;
190 	while (bufferSize > 0) {
191 		// prepare request
192 		remote_disk_header request;
193 		request.offset = htonll(pos);
194 		uint32 toRead = min_c(bufferSize, REMOTE_DISK_BLOCK_SIZE);
195 		request.size = htons(toRead);
196 		request.command = REMOTE_DISK_READ_REQUEST;
197 
198 		// send request
199 		error = _SendRequest(&request, sizeof(request), REMOTE_DISK_READ_REPLY);
200 		if (error != B_OK)
201 			break;
202 
203 		// check for errors
204 		int16 packetSize = ntohs(((remote_disk_header*)fPacket)->size);
205 		if (packetSize < 0) {
206 			if (packetSize == REMOTE_DISK_IO_ERROR)
207 				error = B_IO_ERROR;
208 			else if (packetSize == REMOTE_DISK_BAD_REQUEST)
209 				error = B_BAD_VALUE;
210 			fPacketSize = 0;
211 			break;
212 		}
213 
214 		// read from the packet
215 		size_t packetBytesRead = _ReadFromPacket(pos, buffer, bufferSize);
216 		if (packetBytesRead <= 0) {
217 			if (packetBytesRead < 0)
218 				error = packetBytesRead;
219 			break;
220 		}
221 		bytesRead += packetBytesRead;
222 	}
223 
224 	// only return an error, when we were not able to read anything at all
225 	return (bytesRead == 0 ? error : bytesRead);
226 }
227 
228 
229 // WriteAt
230 ssize_t
231 RemoteDisk::WriteAt(off_t pos, const void *_buffer, size_t bufferSize)
232 {
233 	if (fSocket < 0)
234 		return B_NO_INIT;
235 
236 	const uint8 *buffer = (const uint8*)_buffer;
237 	if (!buffer || pos < 0)
238 		return B_BAD_VALUE;
239 
240 	if (bufferSize == 0)
241 		return 0;
242 
243 	status_t error = B_OK;
244 	size_t bytesWritten = 0;
245 	while (bufferSize > 0) {
246 		// prepare request
247 		remote_disk_header* request = (remote_disk_header*)fPacket;
248 		request->offset = htonll(pos);
249 		uint32 toWrite = min_c(bufferSize, REMOTE_DISK_BLOCK_SIZE);
250 		request->size = htons(toWrite);
251 		request->command = REMOTE_DISK_WRITE_REQUEST;
252 
253 		// copy to packet buffer
254 		if (IS_USER_ADDRESS(buffer)) {
255 			status_t error = user_memcpy(request->data, buffer, toWrite);
256 			if (error != B_OK)
257 				return error;
258 		} else
259 			memcpy(request->data, buffer, toWrite);
260 
261 		// send request
262 		size_t requestSize = request->data + toWrite - (uint8_t*)request;
263 		remote_disk_header reply;
264 		int32 replySize;
265 		error = _SendRequest(request, requestSize, REMOTE_DISK_WRITE_REPLY,
266 			NULL, &reply, sizeof(reply), &replySize);
267 		if (error != B_OK)
268 			break;
269 
270 		// check for errors
271 		int16 packetSize = ntohs(reply.size);
272 		if (packetSize < 0) {
273 			if (packetSize == REMOTE_DISK_IO_ERROR)
274 				error = B_IO_ERROR;
275 			else if (packetSize == REMOTE_DISK_BAD_REQUEST)
276 				error = B_BAD_VALUE;
277 			break;
278 		}
279 
280 		bytesWritten += toWrite;
281 		pos += toWrite;
282 		buffer += toWrite;
283 		bufferSize -= toWrite;
284 	}
285 
286 	// only return an error, when we were not able to write anything at all
287 	return (bytesWritten == 0 ? error : bytesWritten);
288 }
289 
290 
291 // _Init
292 status_t
293 RemoteDisk::_Init()
294 {
295 	// get the socket module
296 	status_t error = get_module(B_SOCKET_MODULE_NAME,
297 		(module_info**)&fSocketModule);
298 	if (error != B_OK) {
299 		dprintf("RemoteDisk::Init(): Failed to load socket module: %s\n",
300 			strerror(error));
301 		return error;
302 	}
303 
304 	// open a control socket for playing with the stack
305 	fSocket = fSocketModule->socket(AF_INET, SOCK_DGRAM, 0);
306 	if (fSocket < 0) {
307 		dprintf("RemoteDisk::Init(): Failed to open socket: %s\n",
308 			strerror(errno));
309 		return errno;
310 	}
311 
312 	// bind socket
313 	fSocketAddress.sin_family = AF_INET;
314 	fSocketAddress.sin_port = 0;
315 	fSocketAddress.sin_addr.s_addr = INADDR_ANY;
316 	fSocketAddress.sin_len = sizeof(sockaddr_in);
317 	if (fSocketModule->bind(fSocket, (sockaddr*)&fSocketAddress,
318 			sizeof(fSocketAddress)) < 0) {
319 		dprintf("RemoteDisk::Init(): Failed to bind socket: %s\n",
320 			strerror(errno));
321 		return errno;
322 	}
323 
324    // get the port
325     socklen_t addrSize = sizeof(fSocketAddress);
326     if (fSocketModule->getsockname(fSocket, (sockaddr*)&fSocketAddress,
327 			&addrSize) < 0) {
328 		dprintf("RemoteDisk::Init(): Failed to get socket address: %s\n",
329 			strerror(errno));
330         return errno;
331     }
332 
333 	// set receive timeout
334 	timeval timeout;
335 	timeout.tv_sec = time_t(kReceiveTimeout / 1000000LL);
336 	timeout.tv_usec = suseconds_t(kReceiveTimeout % 1000000LL);
337 	if (fSocketModule->setsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, &timeout,
338 			sizeof(timeout)) < 0) {
339 		dprintf("RemoteDisk::Init(): Failed to set socket receive timeout: "
340 			"%s\n", strerror(errno));
341         return errno;
342 	}
343 
344 	// allocate buffer
345 	fPacket = malloc(BUFFER_SIZE);
346 	if (!fPacket)
347 		return B_NO_MEMORY;
348 
349 	return B_OK;
350 }
351 
352 
353 // _ReadFromPacket
354 ssize_t
355 RemoteDisk::_ReadFromPacket(off_t& pos, uint8*& buffer, size_t& bufferSize)
356 {
357 	if (fPacketSize == 0)
358 		return 0;
359 
360 	// check whether the cached packet is indeed a read reply
361 	remote_disk_header* header = (remote_disk_header*)fPacket;
362 	if (header->command != REMOTE_DISK_READ_REPLY)
363 		return 0;
364 
365 	uint64 packetOffset = ntohll(header->offset);
366 	uint32 packetSize = ntohs(header->size);
367 	if (packetOffset > (uint64)pos || packetOffset + packetSize <= (uint64)pos)
368 		return 0;
369 
370 	// we have something to copy
371 	size_t toCopy = size_t(packetOffset + packetSize - (uint64)pos);
372 	if (toCopy > bufferSize)
373 		toCopy = bufferSize;
374 
375 	if (IS_USER_ADDRESS(buffer)) {
376 		status_t error = user_memcpy(buffer,
377 			header->data + (pos - packetOffset), toCopy);
378 		if (error != B_OK)
379 			return error;
380 	} else
381 		memcpy(buffer, header->data + (pos - packetOffset), toCopy);
382 
383 	pos += toCopy;
384 	buffer += toCopy;
385 	bufferSize -= toCopy;
386 	return toCopy;
387 }
388 
389 
390 // _SendRequest
391 status_t
392 RemoteDisk::_SendRequest(remote_disk_header* request, size_t size,
393 	uint8 expectedReply, sockaddr_in* peerAddress)
394 {
395 	return _SendRequest(request, size, expectedReply, peerAddress, fPacket,
396 		BUFFER_SIZE, &fPacketSize);
397 }
398 
399 
400 status_t
401 RemoteDisk::_SendRequest(remote_disk_header *request, size_t size,
402 	uint8 expectedReply, sockaddr_in* peerAddress, void* receiveBuffer,
403 	size_t receiveBufferSize, int32* _bytesReceived)
404 {
405 	request->request_id = fRequestID++;
406 	request->port = fSocketAddress.sin_port;
407 
408 	// try sending the request 3 times at most
409 	for (int i = 0; i < 3; i++) {
410 		// send request
411 		ssize_t bytesSent = fSocketModule->sendto(fSocket, request, size,
412         	0, (sockaddr*)&fServerAddress, sizeof(fServerAddress));
413 		if (bytesSent < 0) {
414 			dprintf("RemoteDisk::_SendRequest(): failed to send packet: %s\n",
415 				strerror(errno));
416 			return errno;
417 		}
418 		if (bytesSent != (ssize_t)size) {
419 			dprintf("RemoteDisk::_SendRequest(): sent less bytes than desired\n");
420 			return B_ERROR;
421 		}
422 
423 		// receive reply
424 		bigtime_t timeout = system_time() + kRequestTimeout;
425 		do {
426 			*_bytesReceived = 0;
427 			socklen_t addrSize = sizeof(sockaddr_in);
428 			ssize_t bytesReceived = fSocketModule->recvfrom(fSocket,
429 				receiveBuffer, receiveBufferSize, 0, (sockaddr*)peerAddress,
430 				(peerAddress ? &addrSize : 0));
431 			if (bytesReceived < 0) {
432 				status_t error = errno;
433 				if (error != B_TIMED_OUT && error != B_WOULD_BLOCK)
434 					return error;
435 				continue;
436 			}
437 
438 			// got something; check, if it is looks good
439 			if (bytesReceived >= (ssize_t)sizeof(remote_disk_header)) {
440 				remote_disk_header* reply = (remote_disk_header*)receiveBuffer;
441 				if (reply->request_id == request->request_id
442 					&& reply->command == expectedReply) {
443 					*_bytesReceived = bytesReceived;
444 					return B_OK;
445 				}
446 			}
447 		} while (timeout > system_time());
448 	}
449 
450 	// no reply
451 	return B_TIMED_OUT;
452 }
453