]> git.saurik.com Git - apple/system_cmds.git/blame - msa/PrintBuffer.hpp
system_cmds-671.10.3.tar.gz
[apple/system_cmds.git] / msa / PrintBuffer.hpp
CommitLineData
bd6521f0
A
1//
2// PrintBuffer.hpp
3// system_cmds
4//
5// Created by James McIlree on 5/7/14.
6//
7//
8
9#ifndef __system_cmds__PrintBuffer__
10#define __system_cmds__PrintBuffer__
11
12//
13// Okay, here is how snprintf works.
14//
15// char buf[2];
16//
17// snprintf(buf, 0, "a"); // Returns 1, buf is unchanged.
18// snprintf(buf, 1, "a"); // Returns 1, buf = \0
19// snprintf(buf, 2, "a"); // Returns 1, buf = 'a', \0
20//
21// So... For a buffer of size N, each print is valid if and only if
22// it consumes N-1 bytes.
23//
24
25class PrintBuffer {
26 protected:
27 char* _buffer;
28 size_t _buffer_size;
29 size_t _buffer_capacity;
30 size_t _flush_boundary;
31 int _flush_fd;
32
33 public:
34 PrintBuffer(size_t capacity, size_t flush_boundary, int flush_fd) :
35 _buffer((char*)malloc(capacity)),
36 _buffer_size(0),
37 _buffer_capacity(capacity),
38 _flush_boundary(flush_boundary),
39 _flush_fd(flush_fd)
40 {
41 ASSERT(capacity > 0, "Sanity");
42 ASSERT(_buffer, "Sanity");
43 ASSERT(flush_boundary < capacity, "Sanity");
44 ASSERT(flush_fd != 0, "Must be a valid fd");
45 }
46
47 ~PrintBuffer() {
48 flush();
49 free(_buffer);
50 }
51
52 void set_capacity(size_t capacity) {
53 ASSERT(_buffer_size == 0, "Attempt to reallocate buffer while it still contains data");
54
55 if (_buffer) {
56 free(_buffer);
57 }
58
59 _buffer = (char*)malloc(capacity);
60 _buffer_size = 0;
61 _buffer_capacity = capacity;
62 }
63
64 void flush() {
65 if (_buffer_size) {
66 write(_flush_fd, _buffer, _buffer_size);
67 _buffer_size = 0;
68 }
69 }
70
71 void printf(const char* format, ...) __attribute__((format(printf, 2, 3))) {
72 repeat:
73 size_t remaining_bytes = _buffer_capacity - _buffer_size;
74
75 va_list list;
76 va_start(list, format);
77 int bytes_needed = vsnprintf(&_buffer[_buffer_size], remaining_bytes, format, list);
78 va_end(list);
79
80 // There are three levels of "end" detection.
81 //
82 // 1) If bytes_needed is >= capacity, we must flush, grow capacity, and repeat.
83 // 2) If bytes_needed is >= remaining_bytes, we must flush, and repeat.
84 // 3) If bytes_needed + _buffer_size comes within _flush_boundary bytes of the end, flush.
85 //
86 // NOTE snprintf behavior, we need bytes_needed+1 bytes
87 // to actually fully output all string characters.
88 //
89 // NOTE for any repeat condition, we do not commit the bytes that were written to the buffer.
90 //
91
92 // Condition 2
93 if (bytes_needed >= remaining_bytes) {
94 flush();
95
96 // Save a common path if test by checking this only inside Condition 2
97 //
98 // Condition 1
99 if (bytes_needed >= _buffer_capacity) {
100 set_capacity(bytes_needed+1);
101 }
102
103 goto repeat;
104 }
105
106 // Commit the snprintf
107 _buffer_size += bytes_needed;
108
109 // Condition 3
110 if (remaining_bytes - bytes_needed <= _flush_boundary) {
111 flush();
112 }
113 }
114};
115
116#endif /* defined(__system_cmds__PrintBuffer__) */