1/*
2 * Copyright 2001-2005, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Pahtz <pahtz@yahoo.com.au>
7 *		Axel D��rfler, axeld@pinc-software.de
8 */
9
10/** Class for low-overhead port-based messaging */
11
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <new>
16
17#include <ServerProtocol.h>
18#include <LinkSender.h>
19
20#include "link_message.h"
21#include "syscalls.h"
22
23//#define DEBUG_BPORTLINK
24#ifdef DEBUG_BPORTLINK
25#	include <stdio.h>
26#	define STRACE(x) printf x
27#else
28#	define STRACE(x) ;
29#endif
30
31static const size_t kMaxStringSize = 4096;
32static const size_t kWatermark = kInitialBufferSize - 24;
33	// if a message is started after this mark, the buffer is flushed automatically
34
35namespace BPrivate {
36
37LinkSender::LinkSender(port_id port)
38	:
39	fPort(port),
40	fTargetTeam(-1),
41	fBuffer(NULL),
42	fBufferSize(0),
43
44	fCurrentEnd(0),
45	fCurrentStart(0),
46	fCurrentStatus(B_OK)
47{
48}
49
50
51LinkSender::~LinkSender()
52{
53	free(fBuffer);
54}
55
56
57void
58LinkSender::SetPort(port_id port)
59{
60	fPort = port;
61}
62
63
64status_t
65LinkSender::StartMessage(int32 code, size_t minSize)
66{
67	// end previous message
68	if (EndMessage() < B_OK)
69		CancelMessage();
70
71	if (minSize > kMaxBufferSize - sizeof(message_header)) {
72		// we will handle this case in Attach, using an area
73		minSize = sizeof(area_id);
74	}
75
76	minSize += sizeof(message_header);
77
78	// Eventually flush buffer to make space for the new message.
79	// Note, we do not take the actual buffer size into account to not
80	// delay the time between buffer flushes too much.
81	if (fBufferSize > 0 && (minSize > SpaceLeft() || fCurrentStart >= kWatermark)) {
82		status_t status = Flush();
83		if (status < B_OK)
84			return status;
85	}
86
87	if (minSize > fBufferSize) {
88		if (AdjustBuffer(minSize) != B_OK)
89			return fCurrentStatus = B_NO_MEMORY;
90	}
91
92	message_header *header = (message_header *)(fBuffer + fCurrentStart);
93	header->size = 0;
94		// will be set later
95	header->code = code;
96	header->flags = 0;
97
98	STRACE(("info: LinkSender buffered header %ld (%lx) [%lu %lu %lu].\n",
99		code, code, header->size, header->code, header->flags));
100
101	fCurrentEnd += sizeof(message_header);
102	return B_OK;
103}
104
105
106status_t
107LinkSender::EndMessage(bool needsReply)
108{
109	if (fCurrentEnd == fCurrentStart || fCurrentStatus < B_OK)
110		return fCurrentStatus;
111
112	// record the size of the message
113	message_header *header = (message_header *)(fBuffer + fCurrentStart);
114	header->size = CurrentMessageSize();
115	if (needsReply)
116		header->flags |= needsReply;
117
118	STRACE(("info: LinkSender EndMessage() of size %ld.\n", header->size));
119
120	// bump to start of next message
121	fCurrentStart = fCurrentEnd;
122	return B_OK;
123}
124
125
126void
127LinkSender::CancelMessage()
128{
129	fCurrentEnd = fCurrentStart;
130	fCurrentStatus = B_OK;
131}
132
133
134status_t
135LinkSender::Attach(const void *passedData, size_t passedSize)
136{
137	size_t size = passedSize;
138	const void* data = passedData;
139
140	if (fCurrentStatus < B_OK)
141		return fCurrentStatus;
142
143	if (size == 0)
144		return fCurrentStatus = B_BAD_VALUE;
145
146	if (fCurrentEnd == fCurrentStart)
147		return B_NO_INIT;	// need to call StartMessage() first
148
149	bool useArea = false;
150	if (size >= kMaxBufferSize) {
151		useArea = true;
152		size = sizeof(area_id);
153	}
154
155	if (SpaceLeft() < size) {
156		// we have to make space for the data
157
158		status_t status = FlushCompleted(size + CurrentMessageSize());
159		if (status < B_OK)
160			return fCurrentStatus = status;
161	}
162
163	area_id senderArea = -1;
164	if (useArea) {
165		if (fTargetTeam < 0) {
166			port_info info;
167			status_t result = get_port_info(fPort, &info);
168			if (result != B_OK)
169				return result;
170			fTargetTeam = info.team;
171		}
172		void* address = NULL;
173		off_t alignedSize = (passedSize + B_PAGE_SIZE) & ~(B_PAGE_SIZE - 1);
174		senderArea = create_area("LinkSenderArea", &address, B_ANY_ADDRESS,
175			alignedSize, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
176
177		if (senderArea < B_OK)
178			return senderArea;
179
180		data = &senderArea;
181		memcpy(address, passedData, passedSize);
182
183		area_id areaID = senderArea;
184		senderArea = _kern_transfer_area(senderArea, &address,
185			B_ANY_ADDRESS, fTargetTeam);
186
187		if (senderArea < B_OK) {
188			delete_area(areaID);
189			return senderArea;
190		}
191	}
192
193	memcpy(fBuffer + fCurrentEnd, data, size);
194	fCurrentEnd += size;
195
196	return B_OK;
197}
198
199
200status_t
201LinkSender::AttachString(const char *string, int32 length)
202{
203	if (string == NULL)
204		string = "";
205
206	size_t maxLength = strlen(string);
207	if (length == -1) {
208		length = (int32)maxLength;
209
210		// we should report an error here
211		if (maxLength > kMaxStringSize)
212			length = 0;
213	} else if (length > (int32)maxLength)
214		length = maxLength;
215
216	status_t status = Attach<int32>(length);
217	if (status < B_OK)
218		return status;
219
220	if (length > 0) {
221		status = Attach(string, length);
222		if (status < B_OK)
223			fCurrentEnd -= sizeof(int32);	// rewind the transaction
224	}
225
226	return status;
227}
228
229
230status_t
231LinkSender::AdjustBuffer(size_t newSize, char **_oldBuffer)
232{
233	// make sure the new size is within bounds
234	if (newSize <= kInitialBufferSize)
235		newSize = kInitialBufferSize;
236	else if (newSize > kMaxBufferSize)
237		return B_BUFFER_OVERFLOW;
238	else if (newSize > kInitialBufferSize)
239		newSize = (newSize + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
240
241	if (newSize == fBufferSize) {
242		// keep existing buffer
243		if (_oldBuffer)
244			*_oldBuffer = fBuffer;
245		return B_OK;
246	}
247
248	// create new larger buffer
249	char *buffer = (char *)malloc(newSize);
250	if (buffer == NULL)
251		return B_NO_MEMORY;
252
253	if (_oldBuffer)
254		*_oldBuffer = fBuffer;
255	else
256		free(fBuffer);
257
258	fBuffer = buffer;
259	fBufferSize = newSize;
260	return B_OK;
261}
262
263
264status_t
265LinkSender::FlushCompleted(size_t newBufferSize)
266{
267	// we need to hide the incomplete message so that it's not flushed
268	int32 end = fCurrentEnd;
269	int32 start = fCurrentStart;
270	fCurrentEnd = fCurrentStart;
271
272	status_t status = Flush();
273	if (status < B_OK) {
274		fCurrentEnd = end;
275		return status;
276	}
277
278	char *oldBuffer = NULL;
279	status = AdjustBuffer(newBufferSize, &oldBuffer);
280	if (status != B_OK)
281		return status;
282
283	// move the incomplete message to the start of the buffer
284	fCurrentEnd = end - start;
285	if (oldBuffer != fBuffer) {
286		memcpy(fBuffer, oldBuffer + start, fCurrentEnd);
287		free(oldBuffer);
288	} else
289		memmove(fBuffer, fBuffer + start, fCurrentEnd);
290
291	return B_OK;
292}
293
294
295status_t
296LinkSender::Flush(bigtime_t timeout, bool needsReply)
297{
298	if (fCurrentStatus < B_OK)
299		return fCurrentStatus;
300
301	EndMessage(needsReply);
302	if (fCurrentStart == 0)
303		return B_OK;
304
305	STRACE(("info: LinkSender Flush() waiting to send messages of %ld bytes on port %ld.\n",
306		fCurrentEnd, fPort));
307
308	status_t err;
309	if (timeout != B_INFINITE_TIMEOUT) {
310		do {
311			err = write_port_etc(fPort, kLinkCode, fBuffer,
312				fCurrentEnd, B_RELATIVE_TIMEOUT, timeout);
313		} while (err == B_INTERRUPTED);
314	} else {
315		do {
316			err = write_port(fPort, kLinkCode, fBuffer, fCurrentEnd);
317		} while (err == B_INTERRUPTED);
318	}
319
320	if (err < B_OK) {
321		STRACE(("error info: LinkSender Flush() failed for %ld bytes (%s) on port %ld.\n",
322			fCurrentEnd, strerror(err), fPort));
323		return err;
324	}
325
326	STRACE(("info: LinkSender Flush() messages total of %ld bytes on port %ld.\n",
327		fCurrentEnd, fPort));
328
329	fCurrentEnd = 0;
330	fCurrentStart = 0;
331
332	return B_OK;
333}
334
335}	// namespace BPrivate
336