Notcurses 3.0.16
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
fbuf.h
Go to the documentation of this file.
1#ifndef NOTCURSES_FBUF
2#define NOTCURSES_FBUF
3
4#ifdef __cplusplus
5extern "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
25typedef struct fbuf {
26 uint64_t size;
27 uint64_t used;
28 char* buf;
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.
51static inline int
52fbuf_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.
86static inline int
87fbuf_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.
127static inline int
128fbuf_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.
136static inline int
137fbuf_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
145static inline void
146fbuf_reset(fbuf* f){
147 f->used = 0;
148}
149
150static inline int
151fbuf_reserve(fbuf* f, size_t len){
152 if(fbuf_grow(f, len)){
153 return -1;
154 }
155 return 0;
156}
157
158static inline void
159fbuf_chop(fbuf* f, size_t len){
160 assert(len <= f->size);
161 f->used = len;
162}
163
164static inline int
165fbuf_putc(fbuf* f, char c){
166 if(fbuf_grow(f, 1)){
167 return -1;
168 }
169 f->buf[f->used++] = c;
170 return 1;
171}
172
173static inline int
174fbuf_putn(fbuf* f, const char* s, size_t len){
175 if(fbuf_grow(f, len)){
176 return -1;
177 }
178 memcpy(f->buf + f->used, s, len);
179 f->used += len;
180 return len;
181}
182
183static inline int
184fbuf_puts(fbuf* f, const char* s){
185 size_t slen = strlen(s);
186 return fbuf_putn(f, s, slen);
187}
188
189static inline int
190fbuf_putint(fbuf* f, int n){
191 if(fbuf_grow(f, 20)){ // 64-bit int might require up to 20 digits
192 return -1;
193 }
194 uint64_t r = snprintf(f->buf + f->used, f->size - f->used, "%d", n);
195 if(r > f->size - f->used){
196 assert(r <= f->size - f->used);
197 return -1; // FIXME grow?
198 }
199 f->used += r;
200 return r;
201}
202
203static inline int
204fbuf_putuint(fbuf* f, int n){
205 if(fbuf_grow(f, 20)){ // 64-bit int might require up to 20 digits
206 return -1;
207 }
208 uint64_t r = snprintf(f->buf + f->used, f->size - f->used, "%u", n);
209 if(r > f->size - f->used){
210 assert(r <= f->size - f->used);
211 return -1; // FIXME grow?
212 }
213 f->used += r;
214 return r;
215}
216
217// FIXME eliminate this, ideally
218__attribute__ ((format (printf, 2, 3)))
219static inline int
220fbuf_printf(fbuf* f, const char* fmt, ...){
221 if(fbuf_grow(f, BUFSIZ) < 0){
222 return -1;
223 }
224 va_list va;
226 int r = vsnprintf(f->buf + f->used, f->size - f->used, fmt, va);
228 if((size_t)r >= f->size - f->used){
229 return -1;
230 }
231 assert(r >= 0);
232 f->used += r;
233 return r;
234}
235
236// emit an escape; obviously you can't flush here
237static inline int
238fbuf_emit(fbuf* f, const char* esc){
239 if(!esc){
240 return -1;
241 }
242 if(fbuf_puts(f, esc) < 0){
243 return -1;
244 }
245 return 0;
246}
247
248// releases the resources held by f. f itself is not freed.
249static inline void
250fbuf_free(fbuf* f){
251 if(f){
252// logdebug("Releasing from %" PRIu32 "B (%" PRIu32 "B)", f->size, f->used);
253 if(f->buf){
254#if __linux__
255 if(munmap(f->buf, f->size)){
256 //logwarn("Error unmapping alloc (%s)", strerror(errno));
257 }
258#else
259 free(f->buf);
260#endif
261 f->buf = NULL;
262 }
263 f->size = 0;
264 f->used = 0;
265 }
266}
267
268// write(2) until we've written it all. uses poll(2) to avoid spinning on
269// EAGAIN, at the possible cost of some small latency.
270static inline int
271blocking_write(int fd, const char* buf, size_t buflen){
272//fprintf(stderr, "writing %zu to %d...\n", buflen, fd);
273 size_t written = 0;
274 while(written < buflen){
275 ssize_t w = write(fd, buf + written, buflen - written);
276 if(w < 0){
277 if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR && errno != EBUSY){
278 logerror("Error writing out data on %d (%s)", fd, strerror(errno));
279 return -1;
280 }
281 }else{
282 written += w;
283 }
284 // FIXME ought probably use WSAPoll() on windows
285#ifndef __MINGW32__
286 if(written < buflen){
287 struct pollfd pfd = {
288 .fd = fd,
289 .events = POLLOUT,
290 .revents = 0,
291 };
292 poll(&pfd, 1, -1);
293 }
294#endif
295 }
296 return 0;
297}
298
299// attempt to write the contents of |f| to the FILE |fp|, if there are any
300// contents. reset the fbuf either way.
301static inline int
302fbuf_flush(fbuf* f, FILE* fp){
303 int ret = 0;
304 if(f->used){
305 if(fflush(fp) == EOF){
306 ret = -1;
307 }else if(blocking_write(fileno(fp), f->buf, f->used)){
308 ret = -1;
309 }
310 }
311 fbuf_reset(f);
312 return ret;
313}
314
315// attempt to write the contents of |f| to the FILE |fp|, if there are any
316// contents, and free the fbuf either way.
317static inline int
318fbuf_finalize(fbuf* f, FILE* fp){
319 int ret = 0;
320 if(f->used){
321 if(fflush(fp) == EOF){
322 ret = -1;
323 }else if(blocking_write(fileno(fp), f->buf, f->used)){
324 ret = -1;
325 }
326 }
327 fbuf_free(f);
328 return ret;
329}
330
331#ifdef __cplusplus
332}
333#endif
334
335#endif
const nccell * c
Definition egcpool.h:207
#define MAPFLAGS
Definition fbuf.h:43
const char * fmt
Definition fbuf.h:220
va_end(va)
__attribute__((format(printf, 2, 3))) static inline int fbuf_printf(fbuf *f
const char va_start(va, fmt)
int r
Definition fbuf.h:226
assert(r >=0)
#define logerror(fmt,...)
Definition logging.h:32
vopts n
Definition notcurses.h:3506
API int API int const nccell unsigned len
Definition notcurses.h:2592
Definition fbuf.h:25
char * buf
Definition fbuf.h:28
uint64_t used
Definition fbuf.h:27
uint64_t size
Definition fbuf.h:26
return NULL
Definition termdesc.h:229