The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
dbuff.c
Go to the documentation of this file.
1 /*
2  * This library is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU Lesser General Public
4  * License as published by the Free Software Foundation; either
5  * version 2.1 of the License, or (at your option) any later version.
6  *
7  * This library is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10  * Lesser General Public License for more details.
11  *
12  * You should have received a copy of the GNU Lesser General Public
13  * License along with this library; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17  /** A generic data buffer structure for encoding and decoding
18  *
19  * Because doing manual length checks is error prone and a waste of everyone's time.
20  *
21  * @file src/lib/util/dbuff.c
22  *
23  * @copyright 2020 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
24  */
25 
26 RCSID("$Id: de5ce998e69f5ff8628c640cd21f668e7fc0fd1a $")
27 
28 #include <freeradius-devel/util/dbuff.h>
29 #include <freeradius-devel/util/syserror.h>
30 
31 #if defined(STATIC_ANALYZER) || !defined(NDEBUG)
32 # define CHECK_DBUFF_INIT(_sbuff) do { if (!(_sbuff)->extend && (unlikely(!(_sbuff)->buff) || unlikely(!(_sbuff)->start) || unlikely(!(_sbuff)->end) || unlikely(!(_sbuff)->p))) return 0; } while (0)
33 #else
34 # define CHECK_DBUFF_INIT(_sbuff)
35 #endif
36 
37 DIAG_OFF(overlength-strings) /* Seems broken */
38 /** Internal macro for defining dbuff move functions
39  * @private
40  */
41 #define FR_DBUFF_MOVE_DEF(_out_type, _in_type) \
42 size_t _fr_dbuff_move_##_in_type##_to_##_out_type(fr_##_out_type##_t *out, fr_##_in_type##_t *in, size_t len) \
43 { \
44  size_t ext_len, to_copy, remaining = len; \
45  while (remaining > 0) { \
46  to_copy = remaining; \
47  ext_len = fr_dbuff_extend_lowat(NULL, in, to_copy); \
48  if (ext_len < to_copy) to_copy = ext_len; \
49  ext_len = fr_dbuff_extend_lowat(NULL, out, to_copy); \
50  if (ext_len < to_copy) to_copy = ext_len; \
51  if (to_copy == 0) break; \
52  to_copy = _fr_dbuff_safecpy(fr_dbuff_current(out), fr_dbuff_end(out), \
53  fr_dbuff_current(in), fr_dbuff_current(in) + to_copy); \
54  fr_dbuff_advance(out, (size_t)fr_dbuff_advance(in, to_copy)); \
55  remaining -= to_copy; \
56  } \
57  return len - remaining; \
58 }
59 
60 FR_DBUFF_MOVE_DEF(dbuff, dbuff)
61 FR_DBUFF_MOVE_DEF(dbuff, dbuff_marker)
62 FR_DBUFF_MOVE_DEF(dbuff_marker, dbuff)
63 FR_DBUFF_MOVE_DEF(dbuff_marker, dbuff_marker)
64 DIAG_ON(overlength-strings)
65 
66 static inline CC_HINT(always_inline) size_t min(size_t x, size_t y)
67 {
68  return x < y ? x : y;
69 }
70 
71 /** Update all markers and pointers in the set of dbuffs to point to new_buff
72  *
73  * This function should be used if the underlying buffer is realloced.
74  *
75  * @param[in] dbuff to update.
76  * @param[in] new_buff to assign to to sbuff.
77  * @param[in] new_len Length of the new buffer.
78  */
79 void fr_dbuff_update(fr_dbuff_t *dbuff, uint8_t *new_buff, size_t new_len)
80 {
81  fr_dbuff_t *dbuff_i;
82  uint8_t *old_buff; /* Current buff */
83 
84  old_buff = dbuff->buff;
85 
86  /*
87  * Update pointers to point to positions
88  * in new buffer based on their relative
89  * offsets in the old buffer... but not
90  * past the end of the new buffer.
91  */
92  for (dbuff_i = dbuff; dbuff_i; dbuff_i = dbuff_i->parent) {
93  fr_dbuff_marker_t *m_i;
94 
95  dbuff_i->buff = new_buff;
96  dbuff_i->start = new_buff + min(new_len, dbuff_i->start - old_buff);
97  dbuff_i->end = dbuff_i->buff + new_len;
98  dbuff_i->p = new_buff + min(new_len, dbuff_i->p - old_buff);
99 
100  for (m_i = dbuff_i->m; m_i; m_i = m_i->next) m_i->p = new_buff + min(new_len, m_i->p - old_buff);
101  }
102 }
103 
104 /** Shift the contents of the dbuff, returning the number of bytes we managed to shift
105  *
106  * @param[in] dbuff to shift.
107  * @param[in] shift the number of bytes to shift
108  * (not necessarily the amount actually shifted, because
109  * one can't move pointers outside the buffer)
110  *
111  * @return
112  * - 0 the shift failed due to constraining pointers.
113  * - >0 the number of bytes we managed to shift pointers
114  * in the dbuff.
115  */
116 size_t fr_dbuff_shift(fr_dbuff_t *dbuff, size_t shift)
117 {
118  uint8_t *buff, *end; /* Current start */
119  fr_dbuff_t *dbuff_i;
120  size_t max_shift = shift;
121 
122  CHECK_DBUFF_INIT(dbuff);
123 
124  buff = dbuff->buff;
125  end = dbuff->end;
126 
127  /*
128  * First pass: find the maximum shift, which is the minimum
129  * of the distances from buff to any of the current pointers
130  * or current pointers of markers of dbuff and its ancestors.
131  * (We're also constrained by the requested shift count.)
132  */
133  for (dbuff_i = dbuff; dbuff_i; dbuff_i = dbuff_i->parent) {
134  fr_dbuff_marker_t *m_i;
135 
136  max_shift = min(max_shift, dbuff_i->p - buff);
137  if (!max_shift) return 0;
138 
139  for (m_i = dbuff_i->m; m_i; m_i = m_i->next) {
140  max_shift = min(max_shift, m_i->p - buff);
141  if (!max_shift) return 0;
142  }
143  }
144 
145  /*
146  * Second pass: adjust pointers.
147  * The first pass means we need only subtract shift from current pointers.
148  * Start pointers can't constrain shift, or we'd never free any space, so
149  * they require the added check.
150  * For d->end, d->buff <= d->p - shift, and d->p shouldn't exceed d->end,
151  * so d->p - shift <= d->end - shift, hence d->buff <= d->end - shift.
152  */
153  for (dbuff_i = dbuff; dbuff_i; dbuff_i = dbuff_i->parent) {
154  fr_dbuff_marker_t *m_i;
155  uint8_t *start = dbuff_i->start;
156 
157  dbuff_i->start -= min(max_shift, dbuff_i->start - buff);
158  dbuff_i->p -= max_shift;
159  dbuff_i->end -= max_shift;
160  dbuff_i->shifted += (max_shift - (start - dbuff_i->start));
161  for (m_i = dbuff_i->m; m_i; m_i = m_i->next) m_i->p -= max_shift;
162  }
163 
164  /*
165  * Move data to track moved pointers.
166  */
167  if ((buff + max_shift) < end) memmove(buff, buff + max_shift, end - (buff + max_shift));
168 
169  return max_shift;
170 }
171 
172 /** Refresh the buffer with more data from the file
173  *
174  */
175 size_t _fr_dbuff_extend_fd(fr_dbuff_t *dbuff, size_t extension)
176 {
177  ssize_t bytes_read;
178  size_t available, total_read;
179  fr_dbuff_uctx_fd_t *fctx;
180 
181  CHECK_DBUFF_INIT(dbuff);
182 
183  fctx = dbuff->uctx;
184 
185  if (extension == SIZE_MAX) extension = 0;
186 
187  total_read = dbuff->shifted + (dbuff->end - dbuff->buff);
188  if (total_read >= fctx->max) {
189  fr_strerror_printf("Can't satisfy extension request for %zu bytes", extension);
190  return 0; /* There's no way we could satisfy the extension request */
191  }
192 
193  if (fr_dbuff_used(dbuff)) {
194  /*
195  * Try and shift as much as we can out
196  * of the buffer to make space.
197  *
198  * Note: p and markers are constraints here.
199  */
200  fr_dbuff_shift(dbuff, fr_dbuff_used(dbuff));
201  }
202 
203  available = min(fctx->buff_end - dbuff->end, fctx->max - total_read);
204  if (available < extension) {
205  fr_strerror_printf("Can't satisfy extension request for %zu bytes", extension);
206  return 0; /* There's no way we could satisfy the extension request */
207  }
208 
209  bytes_read = read(fctx->fd, dbuff->end, available);
210 
211  /** Check for errors
212  */
213  if (bytes_read < 0) {
214  fr_strerror_printf("Error extending buffer: %s", fr_syserror(errno));
215  return 0;
216  }
217 
218  /* Question: should extensible ancestors have their ends adjusted? */
219  dbuff->end += bytes_read; /* Advance end, which increases fr_dbuff_remaining() */
220 
221  return bytes_read;
222 }
223 
224 /** Reallocate the current buffer
225  *
226  * @private
227  *
228  * @param[in] dbuff to be extended.
229  * @param[in] extension How many additional bytes should be allocated
230  * in the buffer.
231  * @return
232  * - 0 the extension operation failed.
233  * - >0 the number of bytes the buffer was extended by.
234  */
235 size_t _fr_dbuff_extend_talloc(fr_dbuff_t *dbuff, size_t extension)
236 {
237  fr_dbuff_uctx_talloc_t *tctx = dbuff->uctx;
238  size_t clen, nlen, elen = extension;
239  uint8_t *new_buff;
240 
241  CHECK_DBUFF_INIT(dbuff);
242 
243  clen = dbuff->buff ? talloc_array_length(dbuff->buff) : 0;
244  /*
245  * If the current buffer size + the extension
246  * is less than init, extend the buffer to init.
247  *
248  * This can happen if the buffer has been
249  * trimmed, and then additional data is added.
250  */
251  if ((clen + elen) < tctx->init) {
252  elen = tctx->init - clen;
253  /*
254  * Double the buffer size if it's more than the
255  * requested amount.
256  */
257  } else if (elen < clen){
258  elen = clen;
259  }
260 
261  /*
262  * Check we don't exceed the maximum buffer
263  * length.
264  */
265  if (tctx->max && ((clen + elen) > tctx->max)) {
266  elen = tctx->max - clen;
267  if (elen == 0) {
268  fr_strerror_printf("Failed extending buffer by %zu bytes to "
269  "%zu bytes, max is %zu bytes",
270  extension, clen + extension, tctx->max);
271  return 0;
272  }
273  }
274  nlen = clen + elen;
275 
276  new_buff = talloc_realloc(tctx->ctx, dbuff->buff, uint8_t, nlen);
277  if (unlikely(!new_buff)) {
278  fr_strerror_printf("Failed extending buffer by %zu bytes to %zu bytes", elen, nlen);
279  return 0;
280  }
281 
282  (void)fr_dbuff_update(dbuff, new_buff, nlen); /* Shouldn't fail as we're extending */
283 
284  return elen;
285 }
286 
287 /** Trim a talloced dbuff to the minimum length required to represent the contained string
288  *
289  * @param[in] dbuff to trim.
290  * @param[in] len Length to trim to. Passing SIZE_MAX will
291  * result in the buffer being trimmed to the
292  * length of the content.
293  * @return
294  * - 0 on success.
295  * - -1 on failure - markers present pointing past the end of string data.
296  */
297 int fr_dbuff_trim_talloc(fr_dbuff_t *dbuff, size_t len)
298 {
299  size_t clen = 0, nlen = 0;
300  uint8_t *new_buff;
301  fr_dbuff_uctx_talloc_t *tctx = dbuff->uctx;
302 
303  CHECK_DBUFF_INIT(dbuff);
304 
305  if (dbuff->buff) clen = talloc_array_length(dbuff->buff);
306 
307  if (len != SIZE_MAX) {
308  nlen += len;
309  } else if (dbuff->buff){
310  nlen += (dbuff->p - dbuff->start);
311  }
312 
313  if (nlen != clen) {
314  new_buff = talloc_realloc(tctx->ctx, dbuff->buff, uint8_t, nlen);
315  if (!new_buff) {
316  fr_strerror_printf("Failed trimming buffer from %zu to %zu", clen, nlen);
317  return -1;
318  }
319  fr_dbuff_update(dbuff, new_buff, nlen);
320  }
321 
322  return 0;
323 }
324 
325 /** Reset a talloced buffer to its initial length, clearing any data stored
326  *
327  * @param[in] dbuff to reset.
328  * @return
329  * - 0 on success.
330  * - -1 on failure - markers present pointing past the end of string data.
331  */
333 {
334  fr_dbuff_uctx_talloc_t *tctx = dbuff->uctx;
335 
336  CHECK_DBUFF_INIT(dbuff);
337 
338  fr_dbuff_set_to_start(dbuff); /* Clear data */
339 
340  if (fr_dbuff_used(dbuff) != tctx->init) {
341  uint8_t *new_buff;
342 
343  new_buff = talloc_realloc(tctx->ctx, dbuff->buff, uint8_t, tctx->init);
344  if (!new_buff) {
345  fr_strerror_printf("Failed reallocing from %zu to %zu",
346  talloc_array_length(dbuff->buff), tctx->init);
347  return -1;
348  }
349  dbuff->buff = new_buff;
350  fr_dbuff_update(dbuff, new_buff, tctx->init);
351  }
352 
353  return 0;
354 }
#define RCSID(id)
Definition: build.h:444
#define DIAG_ON(_x)
Definition: build.h:419
#define unlikely(_x)
Definition: build.h:378
#define DIAG_OFF(_x)
Definition: build.h:418
size_t y
Definition: dbuff.c:67
int fr_dbuff_reset_talloc(fr_dbuff_t *dbuff)
Reset a talloced buffer to its initial length, clearing any data stored.
Definition: dbuff.c:332
int fr_dbuff_trim_talloc(fr_dbuff_t *dbuff, size_t len)
Trim a talloced dbuff to the minimum length required to represent the contained string.
Definition: dbuff.c:297
#define CHECK_DBUFF_INIT(_sbuff)
Definition: dbuff.c:32
size_t _fr_dbuff_extend_fd(fr_dbuff_t *dbuff, size_t extension)
Refresh the buffer with more data from the file.
Definition: dbuff.c:175
void fr_dbuff_update(fr_dbuff_t *dbuff, uint8_t *new_buff, size_t new_len)
Update all markers and pointers in the set of dbuffs to point to new_buff.
Definition: dbuff.c:79
#define FR_DBUFF_MOVE_DEF(_out_type, _in_type)
size_t fr_dbuff_shift(fr_dbuff_t *dbuff, size_t shift)
Shift the contents of the dbuff, returning the number of bytes we managed to shift.
Definition: dbuff.c:116
#define fr_dbuff_used(_dbuff_or_marker)
Return the number of bytes remaining between the start of the dbuff or marker and the current positio...
Definition: dbuff.h:762
struct fr_dbuff_marker_s fr_dbuff_marker_t
A position marker associated with a dbuff.
Definition: dbuff.h:81
#define fr_dbuff_set_to_start(_dbuff_or_marker)
Reset the 'current' position of the dbuff or marker to the 'start' of the buffer.
Definition: dbuff.h:1150
size_t _fr_dbuff_extend_talloc(fr_dbuff_t *dbuff, size_t extension)
Reallocate the current buffer.
Definition: dbuff.c:235
uint8_t * p
Definition: merged_model.c:42
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
static size_t min(size_t x, size_t y)
Definition: sbuff.c:135
static char buff[sizeof("18446744073709551615")+3]
Definition: size_tests.c:41
PRIVATE void strings(struct DATA *p, char *tmp)
Definition: snprintf.c:330
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:243
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition: strerror.h:64