1 #ifndef NOTCURSES_FBUF
2 #define NOTCURSES_FBUF
3 
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7 
8 #include <errno.h>
9 #include <assert.h>
10 #include <string.h>
11 #include <stdint.h>
12 #include <unistd.h>
13 #include <inttypes.h>
14 #include "compat/compat.h"
15 #include "logging.h"
16 
17 // a growable buffer into which one can perform formatted i/o, like the
18 // ten thousand that came before it, and the ten trillion which shall
19 // come after. uses mmap (with huge pages, if possible) on unix and
20 // virtualalloc on windows. it can grow arbitrarily large. it does
21 // *not* maintain a NUL terminator, and can hold binary data.
22 // on Windows, we're using VirtualAlloc(). on BSD, we're using realloc().
23 // on Linux, we're using mmap()+mremap().
24 
25 typedef struct fbuf {
26   uint64_t size;
27   uint64_t used;
28   char* buf;
29 } fbuf;
30 
31 // header-only so that we can test it from notcurses-tester
32 
33 #ifdef MAP_POPULATE
34 #ifdef MAP_UNINITIALIZED
35 #define MAPFLAGS (MAP_POPULATE | MAP_UNINITIALIZED)
36 #else
37 #define MAPFLAGS MAP_POPULATE
38 #endif
39 #else
40 #ifdef MAP_UNINITIALIZED
41 #define MAPFLAGS MAP_UNINITIALIZED
42 #else
43 #define MAPFLAGS 0
44 #endif
45 #endif
46 
47 // ensure there is sufficient room to add |n| bytes to |f|. if necessary,
48 // enlarge the buffer, which might move it (invalidating any references
49 // therein). the amount added is based on the current size (and |n|). we
50 // never grow larger than SIZE_MAX / 2.
51 static inline int
fbuf_grow(fbuf * f,size_t n)52 fbuf_grow(fbuf* f, size_t n){
53   assert(NULL != f->buf);
54   assert(0 != f->size);
55   size_t size = f->size;
56   if(size - f->used >= n){
57     return 0; // we have enough space
58   }
59   while(SIZE_MAX / 2 >= size){
60     size *= 2;
61     if(size - f->used < n){
62       continue;
63     }
64     void* tmp;
65 #ifdef __linux__
66     tmp = mremap(f->buf, f->size, size, MREMAP_MAYMOVE);
67     if(tmp == MAP_FAILED){
68       return -1;
69     }
70 #else
71     tmp = realloc(f->buf, size);
72     if(tmp == NULL){
73       return -1;
74     }
75 #endif
76     f->buf = (char*)tmp; // cast for c++ callers
77     f->size = size;
78     return 0;
79   }
80   // n (or our current buffer) is too large
81   return -1;
82 }
83 
84 // prepare (a significant amount of) initial space for the fbuf.
85 // pass 1 for |small| if it ought be...small.
86 static inline int
fbuf_initgrow(fbuf * f,unsigned small)87 fbuf_initgrow(fbuf* f, unsigned small){
88   assert(NULL == f->buf);
89   assert(0 == f->used);
90   assert(0 == f->size);
91   // we start with 2MiB, the huge page size on all of x86+PAE,
92   // ARMv7+LPAE, ARMv8, and x86-64.
93   // FIXME use GetLargePageMinimum() and sysconf
94   size_t size = small ? (4096 > BUFSIZ ? 4096 : BUFSIZ) : 0x200000lu;
95 #if defined(__linux__)
96   /*static bool hugepages_failed = false; // FIXME atomic
97   if(!hugepages_failed && !small){
98     // hugepages don't seem to work with mremap() =[
99     // mmap(2): hugetlb results in automatic stretch out to cover hugepage
100     f->buf = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_HUGETLB |
101                          MAP_PRIVATE | MAP_ANONYMOUS | MAPFLAGS , -1, 0);
102     if(f->buf == MAP_FAILED){
103       hugepages_failed = true;
104       f->buf = NULL;
105     }
106   }
107   if(f->buf == NULL){ // try again without MAP_HUGETLB */
108   f->buf = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE,
109                         MAP_PRIVATE | MAP_ANONYMOUS | MAPFLAGS , -1, 0);
110   //}
111   if(f->buf == MAP_FAILED){
112     f->buf = NULL;
113     return -1;
114   }
115 #else
116   f->buf = (char*)malloc(size);
117   if(f->buf == NULL){
118     return -1;
119   }
120 #endif
121   f->size = size;
122   return 0;
123 }
124 #undef MAPFLAGS
125 
126 // prepare f with a small initial buffer.
127 static inline int
fbuf_init_small(fbuf * f)128 fbuf_init_small(fbuf* f){
129   f->used = 0;
130   f->size = 0;
131   f->buf = NULL;
132   return fbuf_initgrow(f, 1);
133 }
134 
135 // prepare f with a large initial buffer.
136 static inline int
fbuf_init(fbuf * f)137 fbuf_init(fbuf* f){
138   f->used = 0;
139   f->size = 0;
140   f->buf = NULL;
141   return fbuf_initgrow(f, 0);
142 }
143 
144 // reset usage, but don't shrink the buffer or anything
145 static inline void
fbuf_reset(fbuf * f)146 fbuf_reset(fbuf* f){
147   f->used = 0;
148 }
149 
150 static inline int
fbuf_reserve(fbuf * f,size_t len)151 fbuf_reserve(fbuf* f, size_t len){
152   if(fbuf_grow(f, len)){
153     return -1;
154   }
155   return 0;
156 }
157 
158 static inline int
fbuf_putc(fbuf * f,char c)159 fbuf_putc(fbuf* f, char c){
160   if(fbuf_grow(f, 1)){
161     return -1;
162   }
163   f->buf[f->used++] = c;
164   return 1;
165 }
166 
167 static inline int
fbuf_putn(fbuf * f,const char * s,size_t len)168 fbuf_putn(fbuf* f, const char* s, size_t len){
169   if(fbuf_grow(f, len)){
170     return -1;
171   }
172   memcpy(f->buf + f->used, s, len);
173   f->used += len;
174   return len;
175 }
176 
177 static inline int
fbuf_puts(fbuf * f,const char * s)178 fbuf_puts(fbuf* f, const char* s){
179   size_t slen = strlen(s);
180   return fbuf_putn(f, s, slen);
181 }
182 
183 static inline int
fbuf_putint(fbuf * f,int n)184 fbuf_putint(fbuf* f, int n){
185   if(fbuf_grow(f, 10)){ // 32-bit int might require up to 10 digits
186     return -1;
187   }
188   uint64_t r = snprintf(f->buf + f->used, f->size - f->used, "%d", n);
189   if(r > f->size - f->used){
190     assert(r <= f->size - f->used);
191     return -1; // FIXME grow?
192   }
193   f->used += r;
194   return r;
195 }
196 
197 // FIXME eliminate this, ideally
198 __attribute__ ((format (printf, 2, 3)))
199 static inline int
fbuf_printf(fbuf * f,const char * fmt,...)200 fbuf_printf(fbuf* f, const char* fmt, ...){
201   if(fbuf_grow(f, BUFSIZ) < 0){
202     return -1;
203   }
204   va_list va;
205   va_start(va, fmt);
206   int r = vsnprintf(f->buf + f->used, f->size - f->used, fmt, va);
207   va_end(va);
208   if((size_t)r >= f->size - f->used){
209     return -1;
210   }
211   assert(r >= 0);
212   f->used += r;
213   return r;
214 }
215 
216 // emit an escape; obviously you can't flush here
217 static inline int
fbuf_emit(fbuf * f,const char * esc)218 fbuf_emit(fbuf* f, const char* esc){
219   if(!esc){
220     return -1;
221   }
222   if(fbuf_puts(f, esc) < 0){
223     return -1;
224   }
225   return 0;
226 }
227 
228 // releases the resources held by f. f itself is not freed.
229 static inline void
fbuf_free(fbuf * f)230 fbuf_free(fbuf* f){
231   if(f){
232 //    logdebug("Releasing from %" PRIu32 "B (%" PRIu32 "B)\n", f->size, f->used);
233     if(f->buf){
234 #if __linux__
235       if(munmap(f->buf, f->size)){
236         //logwarn("Error unmapping alloc (%s)\n", strerror(errno));
237       }
238 #else
239       free(f->buf);
240 #endif
241       f->buf = NULL;
242     }
243     f->size = 0;
244     f->used = 0;
245   }
246 }
247 
248 // write(2) until we've written it all. uses poll(2) to avoid spinning on
249 // EAGAIN, at the possible cost of some small latency.
250 static inline int
blocking_write(int fd,const char * buf,size_t buflen)251 blocking_write(int fd, const char* buf, size_t buflen){
252 //fprintf(stderr, "writing %zu to %d...\n", buflen, fd);
253   size_t written = 0;
254   while(written < buflen){
255     ssize_t w = write(fd, buf + written, buflen - written);
256     if(w < 0){
257       if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR && errno != EBUSY){
258         logerror("Error writing out data on %d (%s)\n", fd, strerror(errno));
259         return -1;
260       }
261     }else{
262       written += w;
263     }
264     // FIXME ought probably use WSAPoll() on windows
265 #ifndef __MINGW64__
266     if(written < buflen){
267       struct pollfd pfd = {
268         .fd = fd,
269         .events = POLLOUT,
270         .revents = 0,
271       };
272       poll(&pfd, 1, -1);
273     }
274 #endif
275   }
276   return 0;
277 }
278 
279 // attempt to write the contents of |f| to the FILE |fp|, if there are any
280 // contents. reset the fbuf either way.
281 static inline int
fbuf_flush(fbuf * f,FILE * fp)282 fbuf_flush(fbuf* f, FILE* fp){
283   int ret = 0;
284   if(f->used){
285     if(fflush(fp) == EOF){
286       ret = -1;
287     }else if(blocking_write(fileno(fp), f->buf, f->used)){
288       ret = -1;
289     }
290   }
291   fbuf_reset(f);
292   return ret;
293 }
294 
295 // attempt to write the contents of |f| to the FILE |fp|, if there are any
296 // contents, and free the fbuf either way.
297 static inline int
fbuf_finalize(fbuf * f,FILE * fp)298 fbuf_finalize(fbuf* f, FILE* fp){
299   int ret = 0;
300   if(f->used){
301     if(fflush(fp) == EOF){
302       ret = -1;
303     }else if(blocking_write(fileno(fp), f->buf, f->used)){
304       ret = -1;
305     }
306   }
307   fbuf_free(f);
308   return ret;
309 }
310 
311 #ifdef __cplusplus
312 }
313 #endif
314 
315 #endif
316