Rev 16 | Details | Compare with Previous | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line | 
|---|---|---|---|
| 16 | pmbaty | 1 | // buffer.c | 
| 2 | |||
| 3 | // standard C includes | ||
| 4 | #include <stdio.h> | ||
| 5 | #include <stddef.h> | ||
| 6 | #include <stdlib.h> | ||
| 7 | #include <stdarg.h> | ||
| 8 | #include <stdint.h> // for SIZE_MAX | ||
| 9 | #include <string.h> | ||
| 10 | #include <fcntl.h> | ||
| 11 | #include <sys/stat.h> | ||
| 12 | #include <locale.h> | ||
| 13 | #include <errno.h> | ||
| 14 | |||
| 15 | |||
| 16 | // our own includes | ||
| 17 | #include "buffer.h" | ||
| 18 | |||
| 19 | |||
| 20 | // compiler-specific glue | ||
| 21 | #ifdef _MSC_VER | ||
| 22 | #include <io.h> | ||
| 23 | #include <direct.h> | ||
| 24 | #define strdup(s) _strdup((s)) | ||
| 25 | #define mkdir(p,m) _mkdir ((p)) | ||
| 26 | #define strncasecmp(s1,s2,l) _strnicmp ((s1), (s2), (l)) | ||
| 27 | #define strtok_r(s,delim,ctx) strtok_s ((s), (delim), (ctx)) | ||
| 28 | #define fseek(fp,off,m) _fseeki64 ((fp), (off), (m)) | ||
| 29 | #define ftell(fp) _ftelli64 ((fp)) | ||
| 30 | #if (SIZE_MAX == UINT64_MAX) | ||
| 31 | #define ssize_t signed long long | ||
| 32 | #else // SIZE_MAX != UINT64_MAX | ||
| 33 | #define ssize_t signed long | ||
| 34 | #endif // SIZE_MAX == UINT64_MAX | ||
| 35 | #define thread_local __declspec(thread) | ||
| 36 | #else // !_MSC_VER | ||
| 37 | #define thread_local __thread | ||
| 38 | #endif // _MSC_VER | ||
| 39 | |||
| 40 | |||
| 41 | // Windows-specific long path handling facilities | ||
| 42 | #if defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260)) | ||
| 43 | extern void *__stdcall CreateFileW (const wchar_t *lpFileName, unsigned long dwDesiredAccess, unsigned long dwShareMode, void *lpSecurityAttributes, unsigned long dwCreationDisposition, unsigned long dwFlagsAndAttributes, void *hTemplateFile); | ||
| 44 | extern int __stdcall MultiByteToWideChar (unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); | ||
| 45 | extern unsigned long __stdcall GetFullPathNameW (const wchar_t *lpFileName, unsigned long nBufferLength, wchar_t *lpBuffer, wchar_t **lpFilePart); | ||
| 46 | extern int __stdcall CloseHandle (void *hObject); | ||
| 47 | extern int __stdcall AreFileApisANSI (void); | ||
| 48 | thread_local static wchar_t wide_pathname[32768] = L""; | ||
| 49 | thread_local static wchar_t nt_pathname[32768] = L"\\\\?\\"; // initialized to NT prefix once and for all | ||
| 50 | thread_local static void *nt_filehandle = NULL; | ||
| 51 | thread_local static int intermediary_fd = -1; | ||
| 52 | #endif // defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260)) | ||
| 53 | |||
| 54 | |||
| 55 | void Buffer_Forget (buffer_t *buffer) | ||
| 56 | { | ||
| 57 |    // this function forgets a buffer's contents, freeing its associated memory buffer, | ||
| 58 |    // and resets the buffer_t structure. | ||
| 59 | |||
| 60 | if (buffer->bytes != NULL) | ||
| 61 | free (buffer->bytes); // free buffer data | ||
| 62 | buffer->bytes = NULL; | ||
| 63 | buffer->size = 0; // reset data size | ||
| 64 | |||
| 65 | return; // finished, buffer structure is virgin again | ||
| 66 | } | ||
| 67 | |||
| 68 | |||
| 69 | int Buffer_Reset (buffer_t *buffer) | ||
| 70 | { | ||
| 71 |    // this function makes a buffer contain only an empty string (with a null terminator), of size 0. | ||
| 72 |    // Returns 1 on success, 0 on error (in which case errno is set to ENOMEM). | ||
| 73 | |||
| 74 | void *reallocated_ptr; | ||
| 75 | size_t padding; | ||
| 76 | |||
| 77 |    // first, reallocate space for an empty string | ||
| 78 | reallocated_ptr = realloc (buffer->bytes, sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 79 | if (reallocated_ptr == NULL) | ||
| 80 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 81 | |||
| 82 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 83 | buffer->size = 0; // and reset buffer size | ||
| 84 | |||
| 85 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 86 | buffer->bytes[padding] = 0; // always null-terminate buffers, silently | ||
| 87 | |||
| 88 | return (1); // buffer emptied successfully, return SUCCESS | ||
| 89 | } | ||
| 90 | |||
| 91 | |||
| 92 | int Buffer_Append (buffer_t *buffer, const void *data, const size_t data_size) | ||
| 93 | { | ||
| 94 |    // this function appends the contents of data to a buffer's data. | ||
| 95 |    // Buffer is always null-terminated, but the terminator is NOT counted in the buffer's length. | ||
| 96 |    // Returns 1 on success, 0 on error (in which case errno is set to ENOMEM). | ||
| 97 | |||
| 98 | void *reallocated_ptr; | ||
| 99 | size_t padding; | ||
| 100 | |||
| 101 |    // first, reallocate space to hold data_size bytes more | ||
| 102 | reallocated_ptr = realloc (buffer->bytes, buffer->size + data_size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 103 | if (reallocated_ptr == NULL) | ||
| 104 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 105 | |||
| 106 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 107 | if (data_size > 0) | ||
| 108 |    { | ||
| 109 | if (data != NULL) | ||
| 110 | memcpy (&buffer->bytes[buffer->size], data, data_size); // if data points to something, write new data at the end | ||
| 111 |       else | ||
| 112 | memset (&buffer->bytes[buffer->size], 0, data_size); // or, if data is NULL, set the extra space to zero | ||
| 113 | buffer->size += data_size; // and increment buffer size | ||
| 114 |    } | ||
| 115 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 116 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 117 | |||
| 118 | return (1); // buffer appended successfully, return SUCCESS | ||
| 119 | } | ||
| 120 | |||
| 121 | |||
| 122 | int Buffer_AppendFormattedCString (buffer_t *buffer, const char *fmt_string, ...) | ||
| 123 | { | ||
| 124 |    // this function appends some printf-style formatted data to a buffer's data. | ||
| 125 |    // Buffer is always null-terminated, but the terminator is NOT counted in the buffer's length. | ||
| 126 |    // Returns 1 on success, 0 on error (in which case errno is set to ENOMEM). | ||
| 127 | |||
| 128 | void *reallocated_ptr; | ||
| 129 |    va_list varargs_list; | ||
| 130 |    va_list varargs_copy; | ||
| 131 | char *saved_locale; | ||
| 132 | size_t extra_size; | ||
| 133 | size_t padding; | ||
| 134 | |||
| 135 | saved_locale = setlocale (LC_ALL, NULL); // get the current locale | ||
| 136 | if (saved_locale != NULL) | ||
| 137 | saved_locale = strdup (saved_locale); // preserve it | ||
| 138 | setlocale (LC_ALL, "C"); // format everything in the POSIX (international) locale | ||
| 139 | |||
| 140 |    // first, concatenate varargs and see how much extra space we need | ||
| 141 | va_start (varargs_list, fmt_string); | ||
| 142 | va_copy (varargs_copy, varargs_list); | ||
| 143 | extra_size = vsnprintf (NULL, 0, fmt_string, varargs_list); | ||
| 144 | va_end (varargs_list); // release the variable arguments list | ||
| 145 | |||
| 146 |    // now, reallocate space to hold extra_size bytes more | ||
| 147 | reallocated_ptr = realloc (buffer->bytes, buffer->size + extra_size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 148 | if (reallocated_ptr == NULL) | ||
| 149 |    { | ||
| 150 | if (saved_locale != NULL) | ||
| 151 |       { | ||
| 152 | setlocale (LC_ALL, saved_locale); // restore user locale | ||
| 153 | free (saved_locale); // and free the memory we used to back it up | ||
| 154 |       } | ||
| 155 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 156 |    } | ||
| 157 | |||
| 158 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 159 | if (extra_size > 0) | ||
| 160 |    { | ||
| 161 | vsnprintf ((char *) &buffer->bytes[buffer->size], extra_size + 1, fmt_string, varargs_copy); // write new data at the end | ||
| 162 | buffer->size += extra_size; // and increment buffer size | ||
| 163 |    } | ||
| 164 | va_end (varargs_copy); // release the variable arguments list | ||
| 165 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 166 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 167 | |||
| 168 | if (saved_locale != NULL) | ||
| 169 |    { | ||
| 170 | setlocale (LC_ALL, saved_locale); // restore user locale | ||
| 171 | free (saved_locale); // and free the memory we used to back it up | ||
| 172 |    } | ||
| 173 | return (1); // buffer appended successfully, return SUCCESS | ||
| 174 | } | ||
| 175 | |||
| 176 | |||
| 177 | int Buffer_Prepend (buffer_t *buffer, const void *data, const size_t data_size) | ||
| 178 | { | ||
| 179 |    // this function prepends the contents of data to a buffer's data. | ||
| 180 |    // Returns 1 on success, 0 on error (in which case errno is set to ENOMEM). | ||
| 181 | |||
| 182 | void *reallocated_ptr; | ||
| 183 | size_t padding; | ||
| 184 | |||
| 185 |    // first, reallocate space to hold data_size bytes more | ||
| 186 | reallocated_ptr = realloc (buffer->bytes, data_size + buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 187 | if (reallocated_ptr == NULL) | ||
| 188 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 189 | |||
| 190 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 191 | if (data_size > 0) | ||
| 192 |    { | ||
| 193 | #ifdef _MSC_VER | ||
| 194 | #pragma warning(push) | ||
| 195 | #pragma warning(disable:6385) // the Microsoft linter is wrong here. | ||
| 196 | #pragma warning(disable:6386) | ||
| 197 | #endif // _MSC_VER | ||
| 198 | if (buffer->size > 0) | ||
| 199 | memmove (&buffer->bytes[data_size], buffer->bytes, buffer->size); // move existing data to the end | ||
| 200 | memcpy (buffer->bytes, data, data_size); // write new data at the beginning | ||
| 201 | #ifdef _MSC_VER | ||
| 202 | #pragma warning(pop) | ||
| 203 | #endif // _MSC_VER | ||
| 204 | buffer->size += data_size; // and increment buffer size | ||
| 205 |    } | ||
| 206 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 207 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 208 | |||
| 209 | return (1); // buffer appended successfully, return SUCCESS | ||
| 210 | } | ||
| 211 | |||
| 212 | |||
| 213 | int Buffer_PrependFormattedCString (buffer_t *buffer, const char *fmt_string, ...) | ||
| 214 | { | ||
| 215 |    // this function prepends some printf-style formatted data to a buffer's data. | ||
| 216 |    // Buffer is always null-terminated, but the terminator is NOT counted in the buffer's length. | ||
| 217 |    // Returns 1 on success, 0 on error (in which case errno is set to either ENOMEM, EILSEQ or EINVAL). | ||
| 218 | |||
| 219 | void *reallocated_ptr; | ||
| 220 |    va_list varargs_list; | ||
| 221 |    va_list varargs_copy; | ||
| 222 | uint8_t first_byte; | ||
| 223 | char *saved_locale; | ||
| 224 | int extra_size; | ||
| 225 | size_t padding; | ||
| 226 | |||
| 227 | saved_locale = setlocale (LC_ALL, NULL); // get the current locale | ||
| 228 | if (saved_locale != NULL) | ||
| 229 | saved_locale = strdup (saved_locale); // preserve it | ||
| 230 | setlocale (LC_ALL, "C"); // format everything in the POSIX (international) locale | ||
| 231 | |||
| 232 |    // first, concatenate varargs and see how much extra space we need | ||
| 233 | va_start (varargs_list, fmt_string); | ||
| 234 | va_copy (varargs_copy, varargs_list); | ||
| 235 | extra_size = vsnprintf (NULL, 0, fmt_string, varargs_list); | ||
| 236 | va_end (varargs_list); // release the variable arguments list | ||
| 237 | |||
| 238 |    // was there an encoding error ? | ||
| 239 | if (extra_size < 0) | ||
| 240 |    { | ||
| 241 | if (saved_locale != NULL) | ||
| 242 |       { | ||
| 243 | setlocale (LC_ALL, saved_locale); // restore user locale | ||
| 244 | free (saved_locale); // and free the memory we used to back it up | ||
| 245 |       } | ||
| 246 | return (0); // on failure, return an error value (errno already set to either EILSEQ or EINVAL) | ||
| 247 |    } | ||
| 248 | |||
| 249 |    // now, reallocate space to hold extra_size bytes more | ||
| 250 | reallocated_ptr = realloc (buffer->bytes, buffer->size + extra_size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 251 | if (reallocated_ptr == NULL) | ||
| 252 |    { | ||
| 253 | if (saved_locale != NULL) | ||
| 254 |       { | ||
| 255 | setlocale (LC_ALL, saved_locale); // restore user locale | ||
| 256 | free (saved_locale); // and free the memory we used to back it up | ||
| 257 |       } | ||
| 258 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 259 |    } | ||
| 260 | |||
| 261 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 262 | if (extra_size > 0) | ||
| 263 |    { | ||
| 264 | #ifdef _MSC_VER | ||
| 265 | #pragma warning(push) | ||
| 266 | #pragma warning(disable:6001) // the Microsoft linter is wrong here. | ||
| 267 | #pragma warning(disable:6385) | ||
| 268 | #pragma warning(disable:6386) | ||
| 269 | #endif // _MSC_VER | ||
| 270 | if (buffer->size > 0) | ||
| 271 |       { | ||
| 272 | first_byte = buffer->bytes[0]; // remember what the first byte is, since vsnprintf() will overwrite it with a null terminator | ||
| 273 | memmove (&buffer->bytes[extra_size], buffer->bytes, buffer->size); // move existing data to the end | ||
| 274 |       } | ||
| 275 | vsnprintf ((char *) buffer->bytes, (size_t) extra_size + 1, fmt_string, varargs_copy); // write new data at the start (overwriting the first character of the old string with the null terminator) | ||
| 276 | #ifdef _MSC_VER | ||
| 277 | #pragma warning(pop) | ||
| 278 | #endif // _MSC_VER | ||
| 279 | if (buffer->size > 0) | ||
| 280 | buffer->bytes[extra_size] = first_byte; // put back the old first char, which was overwriten by vsnprintf() | ||
| 281 | buffer->size += extra_size; // and increment buffer size | ||
| 282 |    } | ||
| 283 | va_end (varargs_copy); // release the variable arguments list | ||
| 284 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 285 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 286 | |||
| 287 | if (saved_locale != NULL) | ||
| 288 |    { | ||
| 289 | setlocale (LC_ALL, saved_locale); // restore user locale | ||
| 290 | free (saved_locale); // and free the memory we used to back it up | ||
| 291 |    } | ||
| 292 | return (1); // buffer appended successfully, return SUCCESS | ||
| 293 | } | ||
| 294 | |||
| 295 | |||
| 296 | int Buffer_InsertAt (buffer_t *buffer, const size_t insert_index, const void *data, const size_t data_size) | ||
| 297 | { | ||
| 298 |    // this function inserts the contents of data to a buffer's data at position insert_index, | ||
| 299 |    // shifting the remaining data from insert_index data_size forward. | ||
| 300 |    // Returns 1 on success, 0 on error (in which case errno is set to ENOMEM). | ||
| 301 | |||
| 302 | void *reallocated_ptr; | ||
| 303 | size_t padding; | ||
| 304 | |||
| 305 |    // reallocate data buffer at the right size | ||
| 306 | reallocated_ptr = realloc (buffer->bytes, buffer->size + data_size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 307 | if (reallocated_ptr == NULL) | ||
| 308 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 309 | |||
| 310 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 311 | if (insert_index > buffer->size) | ||
| 312 | memset (&buffer->bytes[buffer->size], 0, insert_index - buffer->size); // buffer was upsized: fill the upsized part with zeroes up to insert_index | ||
| 313 | if (data_size > 0) | ||
| 314 |    { | ||
| 315 | memmove (&buffer->bytes[insert_index + data_size], &buffer->bytes[insert_index], buffer->size - insert_index); // move existing data to the end | ||
| 316 | memcpy (&buffer->bytes[insert_index], data, data_size); // write data in place | ||
| 317 | buffer->size += data_size; // update data size | ||
| 318 |    } | ||
| 319 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 320 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 321 | |||
| 322 | return (1); // data successfully inserted into buffer, return SUCCESS | ||
| 323 | } | ||
| 324 | |||
| 325 | |||
| 326 | int Buffer_WriteAt (buffer_t *buffer, const size_t write_index, const void *data, const size_t data_size) | ||
| 327 | { | ||
| 328 |    // this function writes the contents of data to a buffer's data at position write_index, expanding the buffer if necessary. | ||
| 329 |    // Returns 1 on success, 0 on error (in which case errno is set to ENOMEM). | ||
| 330 | |||
| 331 | void *reallocated_ptr; | ||
| 332 | size_t padding; | ||
| 333 | |||
| 334 |    // see if we need to grow the data | ||
| 335 | if (write_index + data_size > buffer->size) | ||
| 336 |    { | ||
| 337 |       // if so, reallocate data buffer at the right size | ||
| 338 | reallocated_ptr = realloc (buffer->bytes, write_index + data_size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 339 | if (reallocated_ptr == NULL) | ||
| 340 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 341 | |||
| 342 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 26 | pmbaty | 343 | if (write_index > buffer->size) | 
| 344 | memset (&buffer->bytes[buffer->size], 0, write_index - buffer->size); // buffer was upsized: fill the upsized part with zeroes up to write_index | ||
| 16 | pmbaty | 345 |    } | 
| 346 | |||
| 347 | if (data_size > 0) | ||
| 348 | memcpy (&buffer->bytes[write_index], data, data_size); // write data in place | ||
| 349 | |||
| 350 | if (write_index + data_size > buffer->size) | ||
| 351 | buffer->size = write_index + data_size; // update data size only if it growed | ||
| 352 |    else | ||
| 353 |    { | ||
| 354 |       // IMPORTANT: data hasn't growed, BUT the passed buffer *MIGHT NOT* have the \0\0\0\0 suffix (e.g. hand-constructed), so we MUST ensure there's space in it to put it! | ||
| 355 | |||
| 356 |       // reallocate data buffer at the right size | ||
| 357 | reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 358 | if (reallocated_ptr == NULL) | ||
| 359 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 360 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 361 |    } | ||
| 362 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 363 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 364 | |||
| 365 | return (1); // buffer written successfully, return SUCCESS | ||
| 366 | } | ||
| 367 | |||
| 368 | |||
| 369 | int Buffer_SubsetFromTo (buffer_t *buffer, const size_t from_index, const size_t to_index) | ||
| 370 | { | ||
| 371 |    // this function shortens the passed buffer by transforming it into its subset ranging | ||
| 372 |    // from from_index (0 meaning beginning of buffer) to to_index (0 *NOT* meaning end of buffer!!! FIXME), | ||
| 373 |    // shortening the buffer as necessary. | ||
| 374 |    // Returns 1 on success, 0 on error (in which case errno is set to E2BIG). | ||
| 375 | |||
| 376 | void *reallocated_ptr; | ||
| 377 | size_t padding; | ||
| 378 | |||
| 379 | if ((from_index > to_index) || (to_index > buffer->size)) | ||
| 380 |    { | ||
| 381 | errno = E2BIG; // set errno to a significant value | ||
| 382 | return (0); // consistency check: return FAILURE if we're requesting an out of bounds subset | ||
| 383 |    } | ||
| 384 | |||
| 385 | buffer->size = to_index - from_index; // report the new buffer size | ||
| 386 | |||
| 387 |    // should we shift the data ? | ||
| 388 | if (from_index > 0) | ||
| 389 | memmove (buffer->bytes, &buffer->bytes[from_index], buffer->size); // shift everything if necessary | ||
| 390 | |||
| 391 |    // IMPORTANT: the passed buffer *MIGHT NOT* have the \0\0\0\0 suffix (e.g. hand-constructed), so we MUST ensure there's space in it to put it! | ||
| 392 | |||
| 393 |    // reallocate data buffer at the right size | ||
| 394 | reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 395 | if (reallocated_ptr == NULL) | ||
| 396 | return (0); // on failure, return an error value (errno is set) | ||
| 397 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 398 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 399 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 400 | |||
| 401 | return (1); // buffer subset isolated successfully, return SUCCESS | ||
| 402 | } | ||
| 403 | |||
| 404 | |||
| 405 | int Buffer_ReadFromFile (buffer_t *buffer, const char *file_pathname) | ||
| 406 | { | ||
| 407 |    // this function copies the contents of file_pathname in a newly allocated data buffer | ||
| 408 |    // and fills in the buffer size accordingly. It is up to the caller to free that buffer. | ||
| 409 |    // Returns 1 on success, 0 on error (in which case errno is set to either EACCESS, ENOMEM or EIO or anything else set by the underlying buffered stream API). | ||
| 410 | |||
| 411 | void *allocated_ptr; | ||
| 412 | int errno_backup; | ||
| 413 | size_t filesize; | ||
| 414 | size_t padding; | ||
| 415 | FILE *fp; // buffered I/O with FILE * is ___FASTER___ than unbuffered I/O with file descriptors | ||
| 416 | |||
| 417 |    // start by reporting a zero length | ||
| 418 | buffer->bytes = NULL; | ||
| 419 | buffer->size = 0; | ||
| 420 | |||
| 421 |    // open file for binary reading (DO NOT stat() BEFORE, ELSE WE WOULD NEED TO APPLY THE SAME LONG PATHNAME WORKAROUND TWICE) | ||
| 422 | #if defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260)) | ||
| 423 |    // on Windows, work around of the MAX_PATH limitation by using the Unicode APIs and NT-style pathnames | ||
| 424 |    // IMPLEMENTATION NOTE: ON WINDOWS, ALL PATHS ARE CONVERTED TO UTF-16 IN MSVCRT.DLL BEFORE THE ACTUAL SYSTEM CALL, | ||
| 425 |    // SO IT *IS* FASTER TO SYSTEMATICALLY CONVERT ALL PATHS TO NT PATHS, LIKE THEY DO IN LLVM/CLANG (see Path.inc) | ||
| 426 | errno = EACCES; // set errno to something significant | ||
| 427 | fp = NULL; // assume failure until told otherwise | ||
| 428 | MultiByteToWideChar (AreFileApisANSI () ? 0 /*CP_ACP*/ : 1 /*CP_OEMCP*/, 0, file_pathname, -1, wide_pathname, sizeof (wide_pathname) / sizeof (wchar_t)); // convert pathname to wide characters, including the NULL terminator | ||
| 429 | GetFullPathNameW (wide_pathname, sizeof (nt_pathname) / sizeof (wchar_t) - 4, &nt_pathname[4], NULL); // canonicalize the resulting pathname and write it just after the NT path prefix | ||
| 430 | nt_filehandle = CreateFileW (nt_pathname, 0x80000000 /*GENERIC_READ*/, 0x3 /*FILE_SHARE_READ|FILE_SHARE_WRITE*/, NULL, 3 /*OPEN_EXISTING*/, 0x80 /*FILE_ATTRIBUTE_NORMAL*/, NULL); // now call CreateFile and get the filesystem object handle | ||
| 431 | if (nt_filehandle != NULL) | ||
| 432 |    { | ||
| 433 | intermediary_fd = _open_osfhandle ((intptr_t) nt_filehandle, O_RDONLY); // and convert this handle back into a POSIX file descriptor | ||
| 434 | if (intermediary_fd != -1) | ||
| 435 |       { | ||
| 436 | fp = _fdopen (intermediary_fd, "rb"); // now convert this handle to a buffered libc file descriptor | ||
| 437 | if (fp == NULL) | ||
| 438 | _close (intermediary_fd); // if we couldn't get a libc file descriptor out of that POSIX file descriptor, close it anyway | ||
| 439 |       } | ||
| 440 |       else | ||
| 441 | CloseHandle (nt_filehandle); // if we couldn't get a POSIX file descriptor out of that handle, close it anyway | ||
| 442 |    } | ||
| 443 | if (fp != NULL) | ||
| 444 | errno = 0; // if file could be opened, clear errno | ||
| 445 | #else // !defined(_WIN32) || (defined(MAXPATHLEN) && (MAXPATHLEN > 260)) | ||
| 446 | fp = fopen (file_pathname, "rb"); // POSIX-compliant system. Good boy. | ||
| 447 | #endif // defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260)) | ||
| 448 | if (fp == NULL) | ||
| 449 | return (0); // on fopen() failure, return an error value (and errno is hopefully set to something meaningful) | ||
| 450 | |||
| 451 |    // measure file size | ||
| 452 | fseek (fp, 0, SEEK_END); | ||
| 453 | filesize = ftell (fp); | ||
| 454 | fseek (fp, 0, SEEK_SET); | ||
| 455 | |||
| 456 |    // allocate enough space for it | ||
| 457 | allocated_ptr = malloc (filesize + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 458 | if (allocated_ptr == NULL) | ||
| 459 |    { | ||
| 460 | errno_backup = errno; // preserve errno on errors | ||
| 461 | fclose (fp); // on malloc() error, close file | ||
| 462 | errno = errno_backup; | ||
| 463 | return (0); // and return an error value (errno already set to ENOMEM) | ||
| 464 |    } | ||
| 465 | |||
| 466 | buffer->bytes = allocated_ptr; // save pointer to newly allocated data | ||
| 467 | |||
| 468 |    // and read it at once | ||
| 469 | if (fread (buffer->bytes, 1, filesize, fp) != filesize) | ||
| 470 |    { | ||
| 471 | errno_backup = (errno ? errno : EIO); // preserve errno on errors; if it isn't set, set it to EIO | ||
| 472 | fclose (fp); // fread() failed, close file | ||
| 473 | free (buffer->bytes); | ||
| 474 | errno = errno_backup; | ||
| 475 | return (0); // on failure, return an error value (errno already set to EIO - or something else set by the underlying buffered stream API) | ||
| 476 |    } | ||
| 477 | |||
| 478 | fclose (fp); // finished, close file | ||
| 479 | buffer->size = filesize; // report its size | ||
| 480 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 481 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 482 | |||
| 483 | return (1); // and return SUCCESS | ||
| 484 | } | ||
| 485 | |||
| 486 | |||
| 487 | int Buffer_WriteToFile (buffer_t *buffer, const char *file_pathname) | ||
| 488 | { | ||
| 489 |    // this function copies the contents of buffer's data into the file pointed to by | ||
| 490 |    // file_pathname. Returns 1 on success, 0 on error (in which case errno is set to either EINVAL, ENOMEM, EACCESS or EIO or anything else set by the underlying buffered stream API). | ||
| 491 |    // NOTE: "<stdout>" and "<stderr>" pathnames can be used to write to stdout and stderr, | ||
| 492 |    // else all directories up to file_pathname are recursively created if necessary. | ||
| 493 | |||
| 494 | size_t string_index; | ||
| 495 | size_t length; | ||
| 496 | int errno_backup; | ||
| 497 | char *temp_pathname; | ||
| 498 | char *separator; | ||
| 499 | char *context; | ||
| 500 | FILE *fp; // buffered I/O with FILE * is ___FASTER___ than unbuffered I/O with file descriptors | ||
| 501 | |||
| 502 |    // do we want to write to the standard output or the standard error? | ||
| 503 | if (strcmp (file_pathname, "<stdout>") == 0) | ||
| 504 | fp = stdout; | ||
| 505 | else if (strcmp (file_pathname, "<stderr>") == 0) | ||
| 506 | fp = stderr; | ||
| 507 |    else | ||
| 508 |    { | ||
| 509 |       // ensure all the path up to file exists, create directories on the fly if needed | ||
| 510 |       // FIXME: this won't work on Windows with symlinks pointing to nonexistent directories... | ||
| 511 | length = strlen (file_pathname); | ||
| 512 | if (length == 0) | ||
| 513 |       { | ||
| 514 | errno = EINVAL; // if the passed pathname is empty, report invalid parameter | ||
| 515 | return (0); // and return an error value | ||
| 516 |       } | ||
| 517 | temp_pathname = strdup (file_pathname); // have a working copy of file_pathname | ||
| 518 | if (temp_pathname == NULL) | ||
| 519 | return (0); // on strdup() failure, return an error value (errno already set to ENOMEM) | ||
| 520 | for (string_index = length - 1; string_index != SIZE_MAX; string_index--) // i.e. loop until it overflows | ||
| 521 | if ((temp_pathname[string_index] == '/') || (temp_pathname[string_index] == '\\')) | ||
| 522 | break; // look for the last directory separator and stop as soon as we find it | ||
| 523 | if (string_index != SIZE_MAX) | ||
| 524 |       { | ||
| 525 | for (; string_index < length; string_index++) | ||
| 526 | temp_pathname[string_index] = 0; // if we found one, break there so as to have just the path and clear the rest of the string | ||
| 527 | context = NULL; | ||
| 528 | separator = strtok_r (&temp_pathname[1], "/\\", &context); // for each separator in the remaining string past the first one... | ||
| 529 | while (separator != NULL) | ||
| 530 |          { | ||
| 531 | (void) mkdir (temp_pathname, 0755); // create directories recursively (FIXME: THIS IS NOT COMPATIBLE WITH LONG FILENAMES) | ||
| 532 | temp_pathname[strlen (temp_pathname)] = '/'; // put back the separator | ||
| 533 | separator = strtok_r (NULL, "/\\", &context); // and look for the next one | ||
| 534 |          } | ||
| 535 |       } | ||
| 536 | free (temp_pathname); // release our working copy of file_pathname | ||
| 537 | |||
| 538 |       // open file for binary writing | ||
| 539 | #if defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260)) | ||
| 540 |       // on Windows, work around of the MAX_PATH limitation by using the Unicode APIs and NT-style pathnames | ||
| 541 |       // IMPLEMENTATION NOTE: ON WINDOWS, ALL PATHS ARE CONVERTED TO UTF-16 IN MSVCRT.DLL BEFORE THE ACTUAL SYSTEM CALL, | ||
| 542 |       // SO IT *IS* FASTER TO SYSTEMATICALLY CONVERT ALL PATHS TO NT PATHS, LIKE THEY DO IN LLVM/CLANG (see Path.inc) | ||
| 543 | errno = EACCES; // set errno to something significant | ||
| 544 | fp = NULL; // assume failure until told otherwise | ||
| 545 | MultiByteToWideChar (AreFileApisANSI () ? 0 /*CP_ACP*/ : 1 /*CP_OEMCP*/, 0, file_pathname, -1, wide_pathname, sizeof (wide_pathname) / sizeof (wchar_t)); // convert pathname to wide characters, including the NULL terminator | ||
| 546 | GetFullPathNameW (wide_pathname, sizeof (nt_pathname) / sizeof (wchar_t) - 4, &nt_pathname[4], NULL); // canonicalize the resulting pathname and write it just after the NT path prefix | ||
| 547 | nt_filehandle = CreateFileW (nt_pathname, 0x40000000 /*GENERIC_WRITE*/, 0x3 /*FILE_SHARE_READ|FILE_SHARE_WRITE*/, NULL, 2 /*CREATE_ALWAYS [MANDATORY for truncation!]*/, 0x80 /*FILE_ATTRIBUTE_NORMAL*/, NULL); // now call CreateFile and get the filesystem object handle | ||
| 548 | if (nt_filehandle != NULL) | ||
| 549 |       { | ||
| 550 | intermediary_fd = _open_osfhandle ((intptr_t) nt_filehandle, O_WRONLY); // and convert this handle back into a POSIX file descriptor | ||
| 551 | if (intermediary_fd != -1) | ||
| 552 |          { | ||
| 553 | fp = _fdopen (intermediary_fd, "wb"); // now convert this handle to a buffered libc file descriptor | ||
| 554 | if (fp == NULL) | ||
| 555 | _close (intermediary_fd); // if we couldn't get a libc file descriptor out of that POSIX file descriptor, close it anyway | ||
| 556 |          } | ||
| 557 |          else | ||
| 558 | CloseHandle (nt_filehandle); // if we couldn't get a POSIX file descriptor out of that handle, close it anyway | ||
| 559 |       } | ||
| 560 | if (fp != NULL) | ||
| 561 | errno = 0; // if file could be opened, clear errno | ||
| 562 | #else // !defined(_WIN32) || (defined(MAXPATHLEN) && (MAXPATHLEN > 260)) | ||
| 563 | fp = fopen (file_pathname, "wb"); // POSIX-compliant system. Good boy. | ||
| 564 | #endif // defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260)) | ||
| 565 | if (fp == NULL) | ||
| 566 | return (0); // on fopen() failure, return an error value (and errno is hopefully set to something meaningful) | ||
| 567 |    } | ||
| 568 | |||
| 569 |    // and write it at once | ||
| 570 | if (fwrite (buffer->bytes, 1, buffer->size, fp) != buffer->size) | ||
| 571 |    { | ||
| 572 | errno_backup = (errno ? errno : EIO); // preserve errno on errors; if it isn't set, set it to EIO | ||
| 573 | if ((fp != stdout) && (fp != stderr)) | ||
| 574 | fclose (fp); // either case, close the file | ||
| 575 | errno = errno_backup; | ||
| 576 | return (0); // on failure, return an error value (errno already set to EIO - or something else set by the underlying buffered stream API) | ||
| 577 |    } | ||
| 578 | |||
| 579 | if ((fp != stdout) && (fp != stderr)) | ||
| 580 | fclose (fp); // finished, close file (only if it was not one of the standard streams) | ||
| 581 | |||
| 582 | errno = 0; // no error | ||
| 583 | return (1); // and return SUCCESS | ||
| 584 | } | ||
| 585 | |||
| 586 | |||
| 587 | uint8_t *Buffer_FindFirst (const buffer_t *buffer, const void *needle, const size_t needle_length) | ||
| 588 | { | ||
| 589 |    // returns a pointer to the first occurence of needle in buffer haystack, or NULL if not found | ||
| 590 | |||
| 591 | size_t byte_index; | ||
| 592 | |||
| 593 | if (buffer->size < needle_length) | ||
| 594 | return (NULL); // consistency check: if buffer is too small for needle, return NULL | ||
| 595 | else if ((buffer->size == needle_length) && (memcmp (buffer->bytes, needle, buffer->size) == 0)) | ||
| 596 | return (buffer->bytes); // special case where needle is exactly as long as buffer | ||
| 597 | |||
| 598 |    // parse buffer from start to end | ||
| 599 | for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED! | ||
| 600 | if (memcmp (&buffer->bytes[byte_index], needle, needle_length) == 0) | ||
| 601 | return (&buffer->bytes[byte_index]); // return the first match we find | ||
| 602 | |||
| 603 | return (NULL); // definitely no needle in buffer | ||
| 604 | } | ||
| 605 | |||
| 606 | |||
| 607 | uint8_t *Buffer_FindFirstCI (const buffer_t *buffer, const char *needle) | ||
| 608 | { | ||
| 609 |    // returns a pointer to the first occurence of needle in buffer haystack (CASE INSENSITIVELY), or NULL if not found | ||
| 610 | |||
| 611 | size_t needle_length; | ||
| 612 | size_t byte_index; | ||
| 613 | |||
| 614 | needle_length = strlen (needle); // measure needle length | ||
| 615 | |||
| 616 | if (buffer->size < needle_length) | ||
| 617 | return (NULL); // consistency check: if buffer is too small for needle, return NULL | ||
| 618 | else if ((buffer->size == needle_length) && (memcmp (buffer->bytes, needle, buffer->size) == 0)) | ||
| 619 | return (buffer->bytes); // special case where needle is exactly as long as buffer | ||
| 620 | |||
| 621 |    // parse buffer from start to end | ||
| 622 | for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED! | ||
| 623 | if (strncasecmp ((const char *) &buffer->bytes[byte_index], needle, needle_length) == 0) | ||
| 624 | return (&buffer->bytes[byte_index]); // return the first match we find | ||
| 625 | |||
| 626 | return (NULL); // definitely no needle in buffer | ||
| 627 | } | ||
| 628 | |||
| 629 | |||
| 630 | uint8_t *Buffer_FindLast (const buffer_t *buffer, const void *needle, const size_t needle_length) | ||
| 631 | { | ||
| 632 |    // returns a pointer to the last occurence of needle in buffer haystack, or NULL if not found | ||
| 633 | |||
| 634 | size_t byte_index; | ||
| 635 | |||
| 636 | if (buffer->size < needle_length) | ||
| 637 | return (NULL); // consistency check: if buffer is too small for needle, return NULL | ||
| 638 | else if ((buffer->size == needle_length) && (memcmp (buffer->bytes, needle, buffer->size) == 0)) | ||
| 639 | return (buffer->bytes); // special case where needle is exactly as long as buffer | ||
| 640 | |||
| 641 |    // parse buffer from end to start | ||
| 642 | for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--) | ||
| 643 | if (memcmp (&buffer->bytes[byte_index], needle, needle_length) == 0) | ||
| 644 | return (&buffer->bytes[byte_index]); // return the last match we find | ||
| 645 | |||
| 646 | return (NULL); // definitely no needle in buffer | ||
| 647 | } | ||
| 648 | |||
| 649 | |||
| 650 | uint8_t *Buffer_FindLastCI (const buffer_t *buffer, const char *needle) | ||
| 651 | { | ||
| 652 |    // returns a pointer to the last occurence of needle in buffer haystack (CASE INSENSITIVELY), or NULL if not found | ||
| 653 | |||
| 654 | size_t needle_length; | ||
| 655 | size_t byte_index; | ||
| 656 | |||
| 657 | needle_length = strlen (needle); // measure needle length | ||
| 658 | |||
| 659 | if (buffer->size < needle_length) | ||
| 660 | return (NULL); // consistency check: if buffer is too small for needle, return NULL | ||
| 661 | else if ((buffer->size == needle_length) && (memcmp (buffer->bytes, needle, buffer->size) == 0)) | ||
| 662 | return (buffer->bytes); // special case where needle is exactly as long as buffer | ||
| 663 | |||
| 664 |    // parse buffer from end to start | ||
| 665 | for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--) | ||
| 666 | if (strncasecmp ((const char *) &buffer->bytes[byte_index], needle, needle_length) == 0) | ||
| 667 | return (&buffer->bytes[byte_index]); // return the last match we find | ||
| 668 | |||
| 669 | return (NULL); // definitely no needle in buffer | ||
| 670 | } | ||
| 671 | |||
| 672 | |||
| 673 | size_t Buffer_Replace (buffer_t *buffer, const void *needle, const size_t needle_length, const void *replacement, const size_t replacement_length, const int howmany_with_zero_for_all_and_minus_for_backwards) | ||
| 674 | { | ||
| 675 |    // replaces as many occurences of needle with replacement in buffer haystack, reallocating as needed. | ||
| 676 |    // If howmany is strictly positive, replacements stop after howmany replacements are made in the forward direction. | ||
| 677 |    // If howmany is strictly negative, replacements stop after -howmany replacements are made in the REVERSE direction. | ||
| 678 |    // Returns the number of replacements made, or 0 if none was made OR an error occured (in which case check errno, which may be E2BIG or ENOMEM). | ||
| 679 | |||
| 680 | void *reallocated_ptr; | ||
| 681 | void *safe_needle; | ||
| 682 | size_t replacements_made; | ||
| 683 | size_t replacements_max; | ||
| 684 | size_t byte_index; | ||
| 685 | size_t padding; | ||
| 686 | int realloc_done; | ||
| 687 | |||
| 688 | if (needle_length > buffer->size) | ||
| 689 |    { | ||
| 690 | errno = E2BIG; // consistency check: if buffer is too small for needle, set errno to E2BIG | ||
| 691 | return (0); // and return an error value | ||
| 692 |    } | ||
| 693 | |||
| 694 | if (needle_length == 0) | ||
| 695 | return (0); // consistency check: if needle is an empty string, return 0 (no replacement made) | ||
| 696 | |||
| 697 |    // is the number of replacements to do NOT exactly one AND does the needle we want to replace already belong to the buffer (meaning it will disappear during replacement) ? | ||
| 698 | if ((abs (howmany_with_zero_for_all_and_minus_for_backwards) != 1) && ((uint8_t *) needle > buffer->bytes) && ((uint8_t *) needle < buffer->bytes + buffer->size)) | ||
| 699 |    { | ||
| 700 | safe_needle = (uint8_t *) malloc (needle_length); // allocate space for a copy of the needle | ||
| 701 | if (safe_needle == NULL) | ||
| 702 | return (0); // on allocation error, return 0 (errno already set to ENOMEM) | ||
| 703 | memcpy (safe_needle, needle, needle_length); // and copy needle somewhere safe | ||
| 704 |    } | ||
| 705 |    else | ||
| 706 | safe_needle = (uint8_t *) needle; // else we can use needle directly and spare us an allocation | ||
| 707 | |||
| 708 |    // see how much we need to replace and in what order | ||
| 709 | replacements_made = 0; | ||
| 710 | realloc_done = 0; | ||
| 711 | if (howmany_with_zero_for_all_and_minus_for_backwards < 0) | ||
| 712 |    { | ||
| 713 |       // backwards direction | ||
| 714 | replacements_max = -howmany_with_zero_for_all_and_minus_for_backwards; | ||
| 715 | |||
| 716 |       // parse buffer from end to start | ||
| 717 | for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--) | ||
| 718 |       { | ||
| 719 | if (memcmp (&buffer->bytes[byte_index], safe_needle, needle_length) != 0) | ||
| 720 | continue; // if there's no match at this location, skip it | ||
| 721 | |||
| 722 |          // a replacement should be made here: see if we need to grow the data | ||
| 723 | if (replacement_length > needle_length) | ||
| 724 |          { | ||
| 725 |             // if so, reallocate data buffer at the right size | ||
| 726 | reallocated_ptr = realloc (buffer->bytes, buffer->size + replacement_length - needle_length + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 727 | if (reallocated_ptr == NULL) | ||
| 728 |             { | ||
| 729 | replacements_made = 0; | ||
| 730 | break; // on failure, return an error value (errno is set) | ||
| 731 |             } | ||
| 732 | |||
| 733 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 734 | realloc_done = 1; // and remember that a reallocation was done | ||
| 735 |          } | ||
| 736 | |||
| 737 |          // move the remaining first if necessary, then put the replacement in place | ||
| 738 | if (needle_length != replacement_length) | ||
| 739 | memmove (&buffer->bytes[byte_index + replacement_length], &buffer->bytes[byte_index + needle_length], buffer->size - (byte_index + needle_length)); | ||
| 740 | if (replacement_length > 0) | ||
| 741 | memcpy (&buffer->bytes[byte_index], replacement, replacement_length); | ||
| 742 | buffer->size += replacement_length - needle_length; // adjust buffer size | ||
| 743 | |||
| 744 | replacements_made++; // one more replacement has been made | ||
| 745 | if ((replacements_max > 0) && (replacements_made >= replacements_max)) | ||
| 746 | break; // if a maximum replacements count was specified, stop here | ||
| 747 |       } | ||
| 748 |    } | ||
| 749 |    else | ||
| 750 |    { | ||
| 751 |       // forward direction | ||
| 752 | replacements_max = howmany_with_zero_for_all_and_minus_for_backwards; | ||
| 753 | |||
| 754 |       // parse buffer from start to end | ||
| 755 | for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED! | ||
| 756 |       { | ||
| 757 | if (memcmp (&buffer->bytes[byte_index], safe_needle, needle_length) != 0) | ||
| 758 | continue; // if there's no match at this location, skip it | ||
| 759 | |||
| 760 |          // a replacement should be made here: see if we need to grow the data | ||
| 761 | if (replacement_length > needle_length) | ||
| 762 |          { | ||
| 763 |             // if so, reallocate data buffer at the right size | ||
| 764 | reallocated_ptr = realloc (buffer->bytes, buffer->size + replacement_length - needle_length + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 765 | if (reallocated_ptr == NULL) | ||
| 766 |             { | ||
| 767 | replacements_made = 0; | ||
| 768 | break; // on failure, return an error value (errno is set) | ||
| 769 |             } | ||
| 770 | |||
| 771 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 772 | realloc_done = 1; // and remember that a reallocation was done | ||
| 773 |          } | ||
| 774 | |||
| 775 |          // move the remaining first (including the null terminator) if necessary, then put the replacement in place | ||
| 776 | if (needle_length != replacement_length) | ||
| 777 | memmove (&buffer->bytes[byte_index + replacement_length], &buffer->bytes[byte_index + needle_length], buffer->size - (byte_index + needle_length)); | ||
| 778 | if (replacement_length > 0) | ||
| 779 | memcpy (&buffer->bytes[byte_index], replacement, replacement_length); | ||
| 780 | buffer->size += replacement_length - needle_length; // adjust buffer size | ||
| 781 | |||
| 782 | replacements_made++; // one more replacement has been made | ||
| 783 | if ((replacements_max > 0) && (replacements_made >= replacements_max)) | ||
| 784 | break; // if a maximum replacements count was specified, stop here | ||
| 785 | |||
| 786 | byte_index += replacement_length - 1; // jump over this replacement and proceed to the remaining of the string | ||
| 787 |       } | ||
| 788 |    } | ||
| 789 | |||
| 790 | if (safe_needle != needle) | ||
| 791 | free (safe_needle); // if we had to allocate space for a safe copy of the needle, free it now | ||
| 792 | |||
| 793 |    // IMPORTANT: in case we haven't reallocated ourselves, the passed buffer *MIGHT NOT* have the \0\0\0\0 suffix (e.g. hand-constructed), so we MUST ensure there's space in it to put it! | ||
| 794 | if (!realloc_done) | ||
| 795 |    { | ||
| 796 |       // reallocate data buffer at the right size | ||
| 797 | reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 798 | if (reallocated_ptr == NULL) | ||
| 799 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 800 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 801 |    } | ||
| 802 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 803 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 804 | |||
| 805 | return (replacements_made); // finished, return the number of replacements made | ||
| 806 | } | ||
| 807 | |||
| 808 | |||
| 809 | int Buffer_Compare (buffer_t *buffer, const void *data, const size_t data_size) | ||
| 810 | { | ||
| 811 |    // compares a buffer with some data of data_size length, and return 0 if they match, non-zero if they differ. | ||
| 812 |    // If their size differ, a non-zero value (equivalent to the string "DIFF") is returned. | ||
| 813 |    // If their content differ, the result of memcmp() is returned. | ||
| 814 | |||
| 815 | if ((data == buffer->bytes) && (data_size == buffer->size)) | ||
| 816 | return (0); // if both buffers are the same one, return 0 | ||
| 817 | else if (data_size != buffer->size) | ||
| 818 | return ((int) ((ssize_t) data_size - (ssize_t) buffer->size)); // buffers differ in size, return the shortest first | ||
| 819 | |||
| 820 | return (memcmp (buffer->bytes, data, data_size)); // do a memcmp() on both buffers | ||
| 821 | } | ||
| 822 | |||
| 823 | |||
| 824 | size_t Buffer_OffsetOf (buffer_t *buffer, const void *something) | ||
| 825 | { | ||
| 826 |    // returns the absolute offset of the data pointed to by something relatively to the beginning of buffer data | ||
| 827 |    // if something points outside of buffer, returns SIZE_MAX (in which case errno is set to EINVAL) | ||
| 828 | |||
| 829 | if (((uint8_t *) something < buffer->bytes) || ((uint8_t *) something > buffer->bytes + buffer->size)) | ||
| 830 |    { | ||
| 831 | errno = EINVAL; // consistency check: if the queried pointer falls outside of the buffer, set errno to EINVAL | ||
| 832 | return (SIZE_MAX); // and return an error code | ||
| 833 |    } | ||
| 834 | |||
| 835 | return ((size_t) something - (size_t) buffer->bytes); // queried pointer lies within buffer: return its offset, which will be between 0 and buffer->size inclusive | ||
| 836 | } | ||
| 837 | |||
| 838 | |||
| 839 | int Buffer_TrimLeftUntil (buffer_t *buffer, const uint8_t *trimmable_bytes, const size_t trimmable_count, const int howmany) | ||
| 840 | { | ||
| 841 |    // removes leading characters from a buffer's data and returns 1 on success, or 0 if a memory | ||
| 842 |    // reallocation error happens (which shouldn't ever since we're reallocating smaller), in which case errno is set to ENOMEM. | ||
| 843 |    // Any character in the trimmable_bytes array is a candidate for elimination. | ||
| 844 | |||
| 845 | void *reallocated_ptr; | ||
| 846 | size_t removed_count; | ||
| 847 | size_t max_count; | ||
| 848 | size_t padding; | ||
| 849 | |||
| 850 | removed_count = 0; // parse buffer string from the end looking for any of the trimmable bytes | ||
| 851 | max_count = (howmany == 0 ? buffer->size : howmany); // see how many characters maximum we are allowed to trim | ||
| 852 | while ((removed_count < max_count) && (memchr (trimmable_bytes, buffer->bytes[removed_count], trimmable_count) != NULL)) | ||
| 853 | removed_count++; // count the number of bytes to remove | ||
| 854 | |||
| 855 |    // did we find something to trim ? | ||
| 856 | if (removed_count > 0) | ||
| 857 |    { | ||
| 858 |       // if so, shift the buffer data start by the number of bytes to remove | ||
| 859 | memmove (buffer->bytes, &buffer->bytes[removed_count], buffer->size - removed_count); | ||
| 860 | buffer->size -= removed_count; // adjust the new buffer size | ||
| 861 | |||
| 862 |       // reallocate data buffer at the right size | ||
| 863 | reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 864 | if (reallocated_ptr == NULL) | ||
| 865 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 866 | |||
| 867 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 868 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 869 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 870 |    } | ||
| 871 | |||
| 872 | return (1); // and return SUCCESS | ||
| 873 | } | ||
| 874 | |||
| 875 | |||
| 876 | int Buffer_TrimRightUntil (buffer_t *buffer, const uint8_t *trimmable_bytes, const size_t trimmable_count, const int howmany) | ||
| 877 | { | ||
| 878 |    // removes trailing characters from a buffer's data and returns 1 on success, or 0 if a memory | ||
| 879 |    // reallocation error happens (which shouldn't ever since we're reallocating smaller), in which case errno is set to ENOMEM. | ||
| 880 |    // Any character in the trimmable_bytes array is a candidate for elimination. | ||
| 881 | |||
| 882 | void *reallocated_ptr; | ||
| 883 | size_t removed_count; | ||
| 884 | size_t max_count; | ||
| 885 | size_t padding; | ||
| 886 | |||
| 887 | removed_count = 0; // parse buffer string from the end looking for any of the trimmable bytes | ||
| 888 | max_count = (howmany == 0 ? buffer->size : howmany); // see how many characters maximum we are allowed to trim | ||
| 889 | while ((buffer->size > 0) && (removed_count < max_count) && (memchr (trimmable_bytes, buffer->bytes[buffer->size - 1], trimmable_count) != NULL)) | ||
| 890 |    { | ||
| 891 | removed_count++; // count the number of bytes to remove | ||
| 892 | buffer->size--; // and decrease buffer size appropriately | ||
| 893 |    } | ||
| 894 | |||
| 895 |    // did we find something to trim ? | ||
| 896 | if (removed_count > 0) | ||
| 897 |    { | ||
| 898 |       // if so, reallocate data buffer at the right size | ||
| 899 | reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator | ||
| 900 | if (reallocated_ptr == NULL) | ||
| 901 | return (0); // on failure, return an error value (errno already set to ENOMEM) | ||
| 902 | |||
| 903 | buffer->bytes = reallocated_ptr; // save pointer to reallocated data | ||
| 904 | for (padding = 0; padding < sizeof (size_t); padding++) | ||
| 905 | buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently | ||
| 906 |    } | ||
| 907 | |||
| 908 | return (1); // and return SUCCESS | ||
| 909 | } | ||
| 910 | |||
| 911 | |||
| 912 | int Buffer_GetNthParts (buffer_t *buffer, const size_t wanted_part_index, const int howmany, const void *separator, const size_t separator_len, const int should_parse_as_text, buffer_t *preinitialized_outbuf) | ||
| 913 | { | ||
| 914 |    // extract and returns the Nth bit of buffer when split according to separator and return whether | ||
| 915 |    // it succeeded or not (in which case errno is set to EINVAL). | ||
| 916 |    // IMPORTANT: preinitialized_outbuf MUST have been properly initialized before! | ||
| 917 | |||
| 918 |    ptrdiff_t extractstart_index; | ||
| 919 | size_t fieldstart_index; | ||
| 920 | size_t char_index; | ||
| 921 | size_t part_index; | ||
| 922 | |||
| 923 |    // handle special case first | ||
| 924 | if (howmany == 0) | ||
| 925 |    { | ||
| 926 | if (preinitialized_outbuf->bytes != NULL) | ||
| 927 | Buffer_Forget (preinitialized_outbuf); // release output buffer if it was previously allocated | ||
| 928 | Buffer_InitWithByteArray (preinitialized_outbuf, ""); // if we want a zero-length slice, prepare an empty buffer | ||
| 929 | return (1); // and return SUCCESS | ||
| 930 |    } | ||
| 931 | |||
| 932 |    // read buffer characters and identify parts on the fly | ||
| 933 | extractstart_index = -1; | ||
| 934 | fieldstart_index = 0; | ||
| 935 | part_index = 0; | ||
| 936 | for (char_index = 0; char_index < buffer->size; char_index++) | ||
| 937 |    { | ||
| 938 |       // have we NOT found yet the start field and is this it ? | ||
| 939 | if ((extractstart_index == -1) && (part_index == wanted_part_index)) | ||
| 940 | extractstart_index = fieldstart_index; // if so, remember where it starts | ||
| 941 | |||
| 942 | if (should_parse_as_text && (buffer->bytes[char_index] == 0)) | ||
| 943 | break; // when end of text reached, stop searching | ||
| 944 | else if ((char_index + separator_len <= buffer->size) && (memcmp (&buffer->bytes[char_index], separator, separator_len) == 0)) | ||
| 945 |       { | ||
| 946 | part_index++; // a separator is here, meaning that one part more was read | ||
| 947 | |||
| 948 | if ((howmany > 0) && (part_index == wanted_part_index + howmany)) | ||
| 949 | break; // was it ALL that we were looking for ? if so, stop searching | ||
| 950 | |||
| 951 | char_index += separator_len; // skip the separator | ||
| 952 | fieldstart_index = char_index; // and remember a new part starts here | ||
| 953 |       } | ||
| 954 |    } | ||
| 955 | |||
| 956 |    // have we found the part we were looking for ? | ||
| 957 | if (extractstart_index != -1) | ||
| 958 |    { | ||
| 959 | if (preinitialized_outbuf->bytes != NULL) | ||
| 960 | Buffer_Forget (preinitialized_outbuf); // release output buffer if it was previously allocated | ||
| 961 | Buffer_InitWithData (preinitialized_outbuf, &buffer->bytes[extractstart_index], char_index - extractstart_index); // if so, so copy it | ||
| 962 | return (1); // and return SUCCESS | ||
| 963 |    } | ||
| 964 | |||
| 965 |    // if we reach here, it means we haven't identified the part we were looking for | ||
| 966 | errno = EINVAL; // so tell the user the wanted_part_index parameter was invalid | ||
| 967 | return (0); // and return FAILURE | ||
| 968 | } |