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 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 51 LinkSender::~LinkSender() 52 { 53 free(fBuffer); 54 } 55 56 57 void 58 LinkSender::SetPort(port_id port) 59 { 60 fPort = port; 61 } 62 63 64 status_t 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 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 127 LinkSender::CancelMessage() 128 { 129 fCurrentEnd = fCurrentStart; 130 fCurrentStatus = B_OK; 131 } 132 133 134 status_t 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 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 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 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 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