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