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