xref: /haiku/src/kits/app/LinkSender.cpp (revision 4f85bc9f1802ec8689957d3c39a2b92715d51a04)
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 
31 static const size_t kMaxStringSize = 4096;
32 static const size_t kWatermark = kInitialBufferSize - 24;
33 	// if a message is started after this mark, the buffer is flushed automatically
34 
35 namespace BPrivate {
36 
LinkSender(port_id port)37 LinkSender::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 
~LinkSender()51 LinkSender::~LinkSender()
52 {
53 	free(fBuffer);
54 }
55 
56 
57 void
SetPort(port_id port)58 LinkSender::SetPort(port_id port)
59 {
60 	fPort = port;
61 }
62 
63 
64 status_t
StartMessage(int32 code,size_t minSize)65 LinkSender::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 
106 status_t
EndMessage(bool needsReply)107 LinkSender::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 
126 void
CancelMessage()127 LinkSender::CancelMessage()
128 {
129 	fCurrentEnd = fCurrentStart;
130 	fCurrentStatus = B_OK;
131 }
132 
133 
134 status_t
Attach(const void * passedData,size_t passedSize)135 LinkSender::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 
200 status_t
AttachString(const char * string,int32 length)201 LinkSender::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 
230 status_t
AdjustBuffer(size_t newSize,char ** _oldBuffer)231 LinkSender::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 
264 status_t
FlushCompleted(size_t newBufferSize)265 LinkSender::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 
295 status_t
Flush(bigtime_t timeout,bool needsReply)296 LinkSender::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