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