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