]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - misc.c
Extensive changes that _should_ fix the socket buffering problems,
[PuTTY.git] / 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 */