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