Extensive changes that _should_ fix the socket buffering problems,
[u/mdw/putty] / misc.c
1 #include <windows.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <assert.h>
5 #include "putty.h"
6
7 /*
8 * Generic routines to deal with send buffers: a linked list of
9 * smallish blocks, with the operations
10 *
11 * - add an arbitrary amount of data to the end of the list
12 * - remove the first N bytes from the list
13 * - return a (pointer,length) pair giving some initial data in
14 * the list, suitable for passing to a send or write system
15 * call
16 * - return the current size of the buffer chain in bytes
17 */
18
19 #define BUFFER_GRANULE 512
20
21 struct bufchain_granule {
22 struct bufchain_granule *next;
23 int buflen, bufpos;
24 char buf[BUFFER_GRANULE];
25 };
26
27 void bufchain_init(bufchain *ch)
28 {
29 ch->head = ch->tail = NULL;
30 ch->buffersize = 0;
31 }
32
33 void bufchain_clear(bufchain *ch)
34 {
35 struct bufchain_granule *b;
36 while (ch->head) {
37 b = ch->head;
38 ch->head = ch->head->next;
39 sfree(b);
40 }
41 ch->tail = NULL;
42 ch->buffersize = 0;
43 }
44
45 int bufchain_size(bufchain *ch)
46 {
47 return ch->buffersize;
48 }
49
50 void bufchain_add(bufchain *ch, void *data, int len)
51 {
52 char *buf = (char *)data;
53
54 ch->buffersize += len;
55
56 if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) {
57 int copylen = min(len, BUFFER_GRANULE - ch->tail->buflen);
58 memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen);
59 buf += copylen;
60 len -= copylen;
61 ch->tail->buflen += copylen;
62 }
63 while (len > 0) {
64 int grainlen = min(len, BUFFER_GRANULE);
65 struct bufchain_granule *newbuf;
66 newbuf = smalloc(sizeof(struct bufchain_granule));
67 newbuf->bufpos = 0;
68 newbuf->buflen = grainlen;
69 memcpy(newbuf->buf, buf, grainlen);
70 buf += grainlen;
71 len -= grainlen;
72 if (ch->tail)
73 ch->tail->next = newbuf;
74 else
75 ch->head = ch->tail = newbuf;
76 newbuf->next = NULL;
77 ch->tail = newbuf;
78 }
79 }
80
81 void bufchain_consume(bufchain *ch, int len)
82 {
83 assert(ch->buffersize >= len);
84 assert(ch->head != NULL && ch->head->bufpos + len <= ch->head->buflen);
85 ch->head->bufpos += len;
86 ch->buffersize -= len;
87 if (ch->head->bufpos >= ch->head->buflen) {
88 struct bufchain_granule *tmp = ch->head;
89 ch->head = tmp->next;
90 sfree(tmp);
91 if (!ch->head)
92 ch->tail = NULL;
93 }
94 }
95
96 void bufchain_prefix(bufchain *ch, void **data, int *len)
97 {
98 *len = ch->head->buflen - ch->head->bufpos;
99 *data = ch->head->buf + ch->head->bufpos;
100 }
101
102 /*
103 * My own versions of malloc, realloc and free. Because I want
104 * malloc and realloc to bomb out and exit the program if they run
105 * out of memory, realloc to reliably call malloc if passed a NULL
106 * pointer, and free to reliably do nothing if passed a NULL
107 * pointer. We can also put trace printouts in, if we need to; and
108 * we can also replace the allocator with an ElectricFence-like
109 * one.
110 */
111
112 #ifdef MINEFIELD
113 /*
114 * Minefield - a Windows equivalent for Electric Fence
115 */
116
117 #define PAGESIZE 4096
118
119 /*
120 * Design:
121 *
122 * We start by reserving as much virtual address space as Windows
123 * will sensibly (or not sensibly) let us have. We flag it all as
124 * invalid memory.
125 *
126 * Any allocation attempt is satisfied by committing one or more
127 * pages, with an uncommitted page on either side. The returned
128 * memory region is jammed up against the _end_ of the pages.
129 *
130 * Freeing anything causes instantaneous decommitment of the pages
131 * involved, so stale pointers are caught as soon as possible.
132 */
133
134 static int minefield_initialised = 0;
135 static void *minefield_region = NULL;
136 static long minefield_size = 0;
137 static long minefield_npages = 0;
138 static long minefield_curpos = 0;
139 static unsigned short *minefield_admin = NULL;
140 static void *minefield_pages = NULL;
141
142 static void minefield_admin_hide(int hide)
143 {
144 int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
145 VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL);
146 }
147
148 static void minefield_init(void)
149 {
150 int size;
151 int admin_size;
152 int i;
153
154 for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) {
155 minefield_region = VirtualAlloc(NULL, size,
156 MEM_RESERVE, PAGE_NOACCESS);
157 if (minefield_region)
158 break;
159 }
160 minefield_size = size;
161
162 /*
163 * Firstly, allocate a section of that to be the admin block.
164 * We'll need a two-byte field for each page.
165 */
166 minefield_admin = minefield_region;
167 minefield_npages = minefield_size / PAGESIZE;
168 admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1);
169 minefield_npages = (minefield_size - admin_size) / PAGESIZE;
170 minefield_pages = (char *) minefield_region + admin_size;
171
172 /*
173 * Commit the admin region.
174 */
175 VirtualAlloc(minefield_admin, minefield_npages * 2,
176 MEM_COMMIT, PAGE_READWRITE);
177
178 /*
179 * Mark all pages as unused (0xFFFF).
180 */
181 for (i = 0; i < minefield_npages; i++)
182 minefield_admin[i] = 0xFFFF;
183
184 /*
185 * Hide the admin region.
186 */
187 minefield_admin_hide(1);
188
189 minefield_initialised = 1;
190 }
191
192 static void minefield_bomb(void)
193 {
194 div(1, *(int *) minefield_pages);
195 }
196
197 static void *minefield_alloc(int size)
198 {
199 int npages;
200 int pos, lim, region_end, region_start;
201 int start;
202 int i;
203
204 npages = (size + PAGESIZE - 1) / PAGESIZE;
205
206 minefield_admin_hide(0);
207
208 /*
209 * Search from current position until we find a contiguous
210 * bunch of npages+2 unused pages.
211 */
212 pos = minefield_curpos;
213 lim = minefield_npages;
214 while (1) {
215 /* Skip over used pages. */
216 while (pos < lim && minefield_admin[pos] != 0xFFFF)
217 pos++;
218 /* Count unused pages. */
219 start = pos;
220 while (pos < lim && pos - start < npages + 2 &&
221 minefield_admin[pos] == 0xFFFF)
222 pos++;
223 if (pos - start == npages + 2)
224 break;
225 /* If we've reached the limit, reset the limit or stop. */
226 if (pos >= lim) {
227 if (lim == minefield_npages) {
228 /* go round and start again at zero */
229 lim = minefield_curpos;
230 pos = 0;
231 } else {
232 minefield_admin_hide(1);
233 return NULL;
234 }
235 }
236 }
237
238 minefield_curpos = pos - 1;
239
240 /*
241 * We have npages+2 unused pages starting at start. We leave
242 * the first and last of these alone and use the rest.
243 */
244 region_end = (start + npages + 1) * PAGESIZE;
245 region_start = region_end - size;
246 /* FIXME: could align here if we wanted */
247
248 /*
249 * Update the admin region.
250 */
251 for (i = start + 2; i < start + npages - 1; i++)
252 minefield_admin[i] = 0xFFFE; /* used but no region starts here */
253 minefield_admin[start + 1] = region_start % PAGESIZE;
254
255 minefield_admin_hide(1);
256
257 VirtualAlloc((char *) minefield_pages + region_start, size,
258 MEM_COMMIT, PAGE_READWRITE);
259 return (char *) minefield_pages + region_start;
260 }
261
262 static void minefield_free(void *ptr)
263 {
264 int region_start, i, j;
265
266 minefield_admin_hide(0);
267
268 region_start = (char *) ptr - (char *) minefield_pages;
269 i = region_start / PAGESIZE;
270 if (i < 0 || i >= minefield_npages ||
271 minefield_admin[i] != region_start % PAGESIZE)
272 minefield_bomb();
273 for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
274 minefield_admin[j] = 0xFFFF;
275 }
276
277 VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT);
278
279 minefield_admin_hide(1);
280 }
281
282 static int minefield_get_size(void *ptr)
283 {
284 int region_start, i, j;
285
286 minefield_admin_hide(0);
287
288 region_start = (char *) ptr - (char *) minefield_pages;
289 i = region_start / PAGESIZE;
290 if (i < 0 || i >= minefield_npages ||
291 minefield_admin[i] != region_start % PAGESIZE)
292 minefield_bomb();
293 for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++);
294
295 minefield_admin_hide(1);
296
297 return j * PAGESIZE - region_start;
298 }
299
300 static void *minefield_c_malloc(size_t size)
301 {
302 if (!minefield_initialised)
303 minefield_init();
304 return minefield_alloc(size);
305 }
306
307 static void minefield_c_free(void *p)
308 {
309 if (!minefield_initialised)
310 minefield_init();
311 minefield_free(p);
312 }
313
314 /*
315 * realloc _always_ moves the chunk, for rapid detection of code
316 * that assumes it won't.
317 */
318 static void *minefield_c_realloc(void *p, size_t size)
319 {
320 size_t oldsize;
321 void *q;
322 if (!minefield_initialised)
323 minefield_init();
324 q = minefield_alloc(size);
325 oldsize = minefield_get_size(p);
326 memcpy(q, p, (oldsize < size ? oldsize : size));
327 minefield_free(p);
328 return q;
329 }
330
331 #endif /* MINEFIELD */
332
333 #ifdef MALLOC_LOG
334 static FILE *fp = NULL;
335
336 void mlog(char *file, int line)
337 {
338 if (!fp) {
339 fp = fopen("putty_mem.log", "w");
340 setvbuf(fp, NULL, _IONBF, BUFSIZ);
341 }
342 if (fp)
343 fprintf(fp, "%s:%d: ", file, line);
344 }
345 #endif
346
347 void *safemalloc(size_t size)
348 {
349 void *p;
350 #ifdef MINEFIELD
351 p = minefield_c_malloc(size);
352 #else
353 p = malloc(size);
354 #endif
355 if (!p) {
356 MessageBox(NULL, "Out of memory!", "PuTTY Fatal Error",
357 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
358 exit(1);
359 }
360 #ifdef MALLOC_LOG
361 if (fp)
362 fprintf(fp, "malloc(%d) returns %p\n", size, p);
363 #endif
364 return p;
365 }
366
367 void *saferealloc(void *ptr, size_t size)
368 {
369 void *p;
370 if (!ptr) {
371 #ifdef MINEFIELD
372 p = minefield_c_malloc(size);
373 #else
374 p = malloc(size);
375 #endif
376 } else {
377 #ifdef MINEFIELD
378 p = minefield_c_realloc(ptr, size);
379 #else
380 p = realloc(ptr, size);
381 #endif
382 }
383 if (!p) {
384 MessageBox(NULL, "Out of memory!", "PuTTY Fatal Error",
385 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
386 exit(1);
387 }
388 #ifdef MALLOC_LOG
389 if (fp)
390 fprintf(fp, "realloc(%p,%d) returns %p\n", ptr, size, p);
391 #endif
392 return p;
393 }
394
395 void safefree(void *ptr)
396 {
397 if (ptr) {
398 #ifdef MALLOC_LOG
399 if (fp)
400 fprintf(fp, "free(%p)\n", ptr);
401 #endif
402 #ifdef MINEFIELD
403 minefield_c_free(ptr);
404 #else
405 free(ptr);
406 #endif
407 }
408 #ifdef MALLOC_LOG
409 else if (fp)
410 fprintf(fp, "freeing null pointer - no action taken\n");
411 #endif
412 }
413
414 #ifdef DEBUG
415 static FILE *debug_fp = NULL;
416 static int debug_got_console = 0;
417
418 static void dputs(char *buf)
419 {
420 DWORD dw;
421
422 if (!debug_got_console) {
423 AllocConsole();
424 debug_got_console = 1;
425 }
426 if (!debug_fp) {
427 debug_fp = fopen("debug.log", "w");
428 }
429
430 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &dw,
431 NULL);
432 fputs(buf, debug_fp);
433 fflush(debug_fp);
434 }
435
436
437 void dprintf(char *fmt, ...)
438 {
439 char buf[2048];
440 va_list ap;
441
442 va_start(ap, fmt);
443 vsprintf(buf, fmt, ap);
444 dputs(buf);
445 va_end(ap);
446 }
447
448
449 void debug_memdump(void *buf, int len, int L)
450 {
451 int i;
452 unsigned char *p = buf;
453 char foo[17];
454 if (L) {
455 int delta;
456 dprintf("\t%d (0x%x) bytes:\n", len, len);
457 delta = 15 & (int) p;
458 p -= delta;
459 len += delta;
460 }
461 for (; 0 < len; p += 16, len -= 16) {
462 dputs(" ");
463 if (L)
464 dprintf("%p: ", p);
465 strcpy(foo, "................"); /* sixteen dots */
466 for (i = 0; i < 16 && i < len; ++i) {
467 if (&p[i] < (unsigned char *) buf) {
468 dputs(" "); /* 3 spaces */
469 foo[i] = ' ';
470 } else {
471 dprintf("%c%02.2x",
472 &p[i] != (unsigned char *) buf
473 && i % 4 ? '.' : ' ', p[i]
474 );
475 if (p[i] >= ' ' && p[i] <= '~')
476 foo[i] = (char) p[i];
477 }
478 }
479 foo[i] = '\0';
480 dprintf("%*s%s\n", (16 - i) * 3 + 2, "", foo);
481 }
482 }
483
484 #endif /* def DEBUG */