// buffer.c
// standard C includes
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h> // for SIZE_MAX
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <locale.h>
#include <errno.h>
// our own includes
#include "buffer.h"
// compiler-specific glue
#ifdef _MSC_VER
#include <io.h>
#include <direct.h>
#define strdup(s) _strdup((s))
#define mkdir(p,m) _mkdir ((p))
#define strncasecmp(s1,s2,l) _strnicmp ((s1), (s2), (l))
#define strtok_r(s,delim,ctx) strtok_s ((s), (delim), (ctx))
#define fseek(fp,off,m) _fseeki64 ((fp), (off), (m))
#define ftell(fp) _ftelli64 ((fp))
#if (SIZE_MAX == UINT64_MAX)
#define ssize_t signed long long
#else // SIZE_MAX != UINT64_MAX
#define ssize_t signed long
#endif // SIZE_MAX == UINT64_MAX
#define thread_local __declspec(thread)
#else // !_MSC_VER
#define thread_local __thread
#endif // _MSC_VER
// Windows-specific long path handling facilities
#if defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
extern void *__stdcall CreateFileW (const wchar_t *lpFileName, unsigned long dwDesiredAccess, unsigned long dwShareMode, void *lpSecurityAttributes, unsigned long dwCreationDisposition, unsigned long dwFlagsAndAttributes, void *hTemplateFile);
extern int __stdcall MultiByteToWideChar (unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar);
extern unsigned long __stdcall GetFullPathNameW (const wchar_t *lpFileName, unsigned long nBufferLength, wchar_t *lpBuffer, wchar_t **lpFilePart);
extern int __stdcall CloseHandle (void *hObject);
extern int __stdcall AreFileApisANSI (void);
thread_local static wchar_t wide_pathname[32768] = L"";
thread_local static wchar_t nt_pathname[32768] = L"\\\\?\\"; // initialized to NT prefix once and for all
thread_local static void *nt_filehandle = NULL;
thread_local static int intermediary_fd = -1;
#endif // defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
void Buffer_Forget (buffer_t *buffer)
{
// this function forgets a buffer's contents, freeing its associated memory buffer,
// and resets the buffer_t structure.
if (buffer->bytes != NULL)
free (buffer
->bytes
); // free buffer data
buffer->bytes = NULL;
buffer->size = 0; // reset data size
return; // finished, buffer structure is virgin again
}
int Buffer_Reset (buffer_t *buffer)
{
// this function makes a buffer contain only an empty string (with a null terminator), of size 0.
// Returns 1 on success, 0 on error (in which case errno is set to ENOMEM).
void *reallocated_ptr;
size_t padding;
// first, reallocate space for an empty string
reallocated_ptr
= realloc (buffer
->bytes
, sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
buffer->size = 0; // and reset buffer size
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[padding] = 0; // always null-terminate buffers, silently
return (1); // buffer emptied successfully, return SUCCESS
}
int Buffer_Append (buffer_t *buffer, const void *data, const size_t data_size)
{
// this function appends the contents of data to a buffer's data.
// Buffer is always null-terminated, but the terminator is NOT counted in the buffer's length.
// Returns 1 on success, 0 on error (in which case errno is set to ENOMEM).
void *reallocated_ptr;
size_t padding;
// first, reallocate space to hold data_size bytes more
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ data_size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
if (data_size > 0)
{
if (data != NULL)
memcpy (&buffer
->bytes
[buffer
->size
], data
, data_size
); // if data points to something, write new data at the end
else
memset (&buffer
->bytes
[buffer
->size
], 0, data_size
); // or, if data is NULL, set the extra space to zero
buffer->size += data_size; // and increment buffer size
}
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
return (1); // buffer appended successfully, return SUCCESS
}
int Buffer_AppendFormattedCString (buffer_t *buffer, const char *fmt_string, ...)
{
// this function appends some printf-style formatted data to a buffer's data.
// Buffer is always null-terminated, but the terminator is NOT counted in the buffer's length.
// Returns 1 on success, 0 on error (in which case errno is set to ENOMEM).
void *reallocated_ptr;
va_list varargs_list;
va_list varargs_copy;
char *saved_locale;
size_t extra_size;
size_t padding;
saved_locale
= setlocale (LC_ALL
, NULL
); // get the current locale
if (saved_locale != NULL)
saved_locale = strdup (saved_locale); // preserve it
setlocale (LC_ALL
, "C"); // format everything in the POSIX (international) locale
// first, concatenate varargs and see how much extra space we need
va_copy (varargs_copy
, varargs_list
);
extra_size = vsnprintf (NULL, 0, fmt_string, varargs_list);
va_end (varargs_list
); // release the variable arguments list
// now, reallocate space to hold extra_size bytes more
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ extra_size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
{
if (saved_locale != NULL)
{
setlocale (LC_ALL
, saved_locale
); // restore user locale
free (saved_locale
); // and free the memory we used to back it up
}
return (0); // on failure, return an error value (errno already set to ENOMEM)
}
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
if (extra_size > 0)
{
vsnprintf ((char *) &buffer->bytes[buffer->size], extra_size + 1, fmt_string, varargs_copy); // write new data at the end
buffer->size += extra_size; // and increment buffer size
}
va_end (varargs_copy
); // release the variable arguments list
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
if (saved_locale != NULL)
{
setlocale (LC_ALL
, saved_locale
); // restore user locale
free (saved_locale
); // and free the memory we used to back it up
}
return (1); // buffer appended successfully, return SUCCESS
}
int Buffer_Prepend (buffer_t *buffer, const void *data, const size_t data_size)
{
// this function prepends the contents of data to a buffer's data.
// Returns 1 on success, 0 on error (in which case errno is set to ENOMEM).
void *reallocated_ptr;
size_t padding;
// first, reallocate space to hold data_size bytes more
reallocated_ptr
= realloc (buffer
->bytes
, data_size
+ buffer
->size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
if (data_size > 0)
{
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:6385) // the Microsoft linter is wrong here.
#pragma warning(disable:6386)
#endif // _MSC_VER
if (buffer->size > 0)
memmove (&buffer
->bytes
[data_size
], buffer
->bytes
, buffer
->size
); // move existing data to the end
memcpy (buffer
->bytes
, data
, data_size
); // write new data at the beginning
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
buffer->size += data_size; // and increment buffer size
}
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
return (1); // buffer appended successfully, return SUCCESS
}
int Buffer_PrependFormattedCString (buffer_t *buffer, const char *fmt_string, ...)
{
// this function prepends some printf-style formatted data to a buffer's data.
// Buffer is always null-terminated, but the terminator is NOT counted in the buffer's length.
// Returns 1 on success, 0 on error (in which case errno is set to either ENOMEM, EILSEQ or EINVAL).
void *reallocated_ptr;
va_list varargs_list;
va_list varargs_copy;
uint8_t first_byte;
char *saved_locale;
int extra_size;
size_t padding;
saved_locale
= setlocale (LC_ALL
, NULL
); // get the current locale
if (saved_locale != NULL)
saved_locale = strdup (saved_locale); // preserve it
setlocale (LC_ALL
, "C"); // format everything in the POSIX (international) locale
// first, concatenate varargs and see how much extra space we need
va_copy (varargs_copy
, varargs_list
);
extra_size = vsnprintf (NULL, 0, fmt_string, varargs_list);
va_end (varargs_list
); // release the variable arguments list
// was there an encoding error ?
if (extra_size < 0)
{
if (saved_locale != NULL)
{
setlocale (LC_ALL
, saved_locale
); // restore user locale
free (saved_locale
); // and free the memory we used to back it up
}
return (0); // on failure, return an error value (errno already set to either EILSEQ or EINVAL)
}
// now, reallocate space to hold extra_size bytes more
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ extra_size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
{
if (saved_locale != NULL)
{
setlocale (LC_ALL
, saved_locale
); // restore user locale
free (saved_locale
); // and free the memory we used to back it up
}
return (0); // on failure, return an error value (errno already set to ENOMEM)
}
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
if (extra_size > 0)
{
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:6001) // the Microsoft linter is wrong here.
#pragma warning(disable:6385)
#pragma warning(disable:6386)
#endif // _MSC_VER
if (buffer->size > 0)
{
first_byte = buffer->bytes[0]; // remember what the first byte is, since vsnprintf() will overwrite it with a null terminator
memmove (&buffer
->bytes
[extra_size
], buffer
->bytes
, buffer
->size
); // move existing data to the end
}
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)
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
if (buffer->size > 0)
buffer->bytes[extra_size] = first_byte; // put back the old first char, which was overwriten by vsnprintf()
buffer->size += extra_size; // and increment buffer size
}
va_end (varargs_copy
); // release the variable arguments list
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
if (saved_locale != NULL)
{
setlocale (LC_ALL
, saved_locale
); // restore user locale
free (saved_locale
); // and free the memory we used to back it up
}
return (1); // buffer appended successfully, return SUCCESS
}
int Buffer_InsertAt (buffer_t *buffer, const size_t insert_index, const void *data, const size_t data_size)
{
// this function inserts the contents of data to a buffer's data at position insert_index,
// shifting the remaining data from insert_index data_size forward.
// Returns 1 on success, 0 on error (in which case errno is set to ENOMEM).
void *reallocated_ptr;
size_t padding;
// reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ data_size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
if (insert_index > buffer->size)
memset (&buffer
->bytes
[buffer
->size
], 0, insert_index
- buffer
->size
); // buffer was upsized: fill the upsized part with zeroes up to insert_index
if (data_size > 0)
{
memmove (&buffer
->bytes
[insert_index
+ data_size
], &buffer
->bytes
[insert_index
], buffer
->size
- insert_index
); // move existing data to the end
memcpy (&buffer
->bytes
[insert_index
], data
, data_size
); // write data in place
buffer->size += data_size; // update data size
}
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
return (1); // data successfully inserted into buffer, return SUCCESS
}
int Buffer_WriteAt (buffer_t *buffer, const size_t write_index, const void *data, const size_t data_size)
{
// this function writes the contents of data to a buffer's data at position write_index, expanding the buffer if necessary.
// Returns 1 on success, 0 on error (in which case errno is set to ENOMEM).
void *reallocated_ptr;
size_t padding;
// see if we need to grow the data
if (write_index + data_size > buffer->size)
{
// if so, reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, write_index
+ data_size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
if (write_index > buffer->size)
memset (&buffer
->bytes
[buffer
->size
], 0, write_index
- buffer
->size
); // buffer was upsized: fill the upsized part with zeroes up to write_index
}
if (data_size > 0)
memcpy (&buffer
->bytes
[write_index
], data
, data_size
); // write data in place
if (write_index + data_size > buffer->size)
buffer->size = write_index + data_size; // update data size only if it growed
else
{
// 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!
// reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
}
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
return (1); // buffer written successfully, return SUCCESS
}
int Buffer_SubsetFromTo (buffer_t *buffer, const size_t from_index, const size_t to_index)
{
// this function shortens the passed buffer by transforming it into its subset ranging
// from from_index (0 meaning beginning of buffer) to to_index (0 *NOT* meaning end of buffer!!! FIXME),
// shortening the buffer as necessary.
// Returns 1 on success, 0 on error (in which case errno is set to E2BIG).
void *reallocated_ptr;
size_t padding;
if ((from_index > to_index) || (to_index > buffer->size))
{
errno = E2BIG; // set errno to a significant value
return (0); // consistency check: return FAILURE if we're requesting an out of bounds subset
}
buffer->size = to_index - from_index; // report the new buffer size
// should we shift the data ?
if (from_index > 0)
memmove (buffer
->bytes
, &buffer
->bytes
[from_index
], buffer
->size
); // shift everything if necessary
// 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!
// reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno is set)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
return (1); // buffer subset isolated successfully, return SUCCESS
}
int Buffer_ReadFromFile (buffer_t *buffer, const char *file_pathname)
{
// this function copies the contents of file_pathname in a newly allocated data buffer
// and fills in the buffer size accordingly. It is up to the caller to free that buffer.
// 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).
void *allocated_ptr;
int errno_backup;
size_t filesize;
size_t padding;
FILE *fp; // buffered I/O with FILE * is ___FASTER___ than unbuffered I/O with file descriptors
// start by reporting a zero length
buffer->bytes = NULL;
buffer->size = 0;
// open file for binary reading (DO NOT stat() BEFORE, ELSE WE WOULD NEED TO APPLY THE SAME LONG PATHNAME WORKAROUND TWICE)
#if defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
// on Windows, work around of the MAX_PATH limitation by using the Unicode APIs and NT-style pathnames
// IMPLEMENTATION NOTE: ON WINDOWS, ALL PATHS ARE CONVERTED TO UTF-16 IN MSVCRT.DLL BEFORE THE ACTUAL SYSTEM CALL,
// SO IT *IS* FASTER TO SYSTEMATICALLY CONVERT ALL PATHS TO NT PATHS, LIKE THEY DO IN LLVM/CLANG (see Path.inc)
errno = EACCES; // set errno to something significant
fp = NULL; // assume failure until told otherwise
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
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
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
if (nt_filehandle != NULL)
{
intermediary_fd = _open_osfhandle ((intptr_t) nt_filehandle, O_RDONLY); // and convert this handle back into a POSIX file descriptor
if (intermediary_fd != -1)
{
fp = _fdopen (intermediary_fd, "rb"); // now convert this handle to a buffered libc file descriptor
if (fp == NULL)
_close (intermediary_fd); // if we couldn't get a libc file descriptor out of that POSIX file descriptor, close it anyway
}
else
CloseHandle (nt_filehandle); // if we couldn't get a POSIX file descriptor out of that handle, close it anyway
}
if (fp != NULL)
errno = 0; // if file could be opened, clear errno
#else // !defined(_WIN32) || (defined(MAXPATHLEN) && (MAXPATHLEN > 260))
fp
= fopen (file_pathname
, "rb"); // POSIX-compliant system. Good boy.
#endif // defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
if (fp == NULL)
return (0); // on fopen() failure, return an error value (and errno is hopefully set to something meaningful)
// measure file size
// allocate enough space for it
allocated_ptr
= malloc (filesize
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (allocated_ptr == NULL)
{
errno_backup = errno; // preserve errno on errors
fclose (fp
); // on malloc() error, close file
errno = errno_backup;
return (0); // and return an error value (errno already set to ENOMEM)
}
buffer->bytes = allocated_ptr; // save pointer to newly allocated data
// and read it at once
if (fread (buffer
->bytes
, 1, filesize
, fp
) != filesize
)
{
errno_backup = (errno ? errno : EIO); // preserve errno on errors; if it isn't set, set it to EIO
fclose (fp
); // fread() failed, close file
errno = errno_backup;
return (0); // on failure, return an error value (errno already set to EIO - or something else set by the underlying buffered stream API)
}
fclose (fp
); // finished, close file
buffer->size = filesize; // report its size
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
return (1); // and return SUCCESS
}
int Buffer_WriteToFile (buffer_t *buffer, const char *file_pathname)
{
// this function copies the contents of buffer's data into the file pointed to by
// 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).
// NOTE: "<stdout>" and "<stderr>" pathnames can be used to write to stdout and stderr,
// else all directories up to file_pathname are recursively created if necessary.
size_t string_index;
size_t length;
int errno_backup;
char *temp_pathname;
char *separator;
char *context;
FILE *fp; // buffered I/O with FILE * is ___FASTER___ than unbuffered I/O with file descriptors
// do we want to write to the standard output or the standard error?
if (strcmp (file_pathname
, "<stdout>") == 0)
fp = stdout;
else if (strcmp (file_pathname
, "<stderr>") == 0)
fp = stderr;
else
{
// ensure all the path up to file exists, create directories on the fly if needed
// FIXME: this won't work on Windows with symlinks pointing to nonexistent directories...
length
= strlen (file_pathname
);
if (length == 0)
{
errno = EINVAL; // if the passed pathname is empty, report invalid parameter
return (0); // and return an error value
}
temp_pathname = strdup (file_pathname); // have a working copy of file_pathname
if (temp_pathname == NULL)
return (0); // on strdup() failure, return an error value (errno already set to ENOMEM)
for (string_index = length - 1; string_index != SIZE_MAX; string_index--) // i.e. loop until it overflows
if ((temp_pathname[string_index] == '/') || (temp_pathname[string_index] == '\\'))
break; // look for the last directory separator and stop as soon as we find it
if (string_index != SIZE_MAX)
{
for (; string_index < length; string_index++)
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
context = NULL;
separator = strtok_r (&temp_pathname[1], "/\\", &context); // for each separator in the remaining string past the first one...
while (separator != NULL)
{
(void) mkdir (temp_pathname, 0755); // create directories recursively (FIXME: THIS IS NOT COMPATIBLE WITH LONG FILENAMES)
temp_pathname
[strlen (temp_pathname
)] = '/'; // put back the separator
separator = strtok_r (NULL, "/\\", &context); // and look for the next one
}
}
free (temp_pathname
); // release our working copy of file_pathname
// open file for binary writing
#if defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
// on Windows, work around of the MAX_PATH limitation by using the Unicode APIs and NT-style pathnames
// IMPLEMENTATION NOTE: ON WINDOWS, ALL PATHS ARE CONVERTED TO UTF-16 IN MSVCRT.DLL BEFORE THE ACTUAL SYSTEM CALL,
// SO IT *IS* FASTER TO SYSTEMATICALLY CONVERT ALL PATHS TO NT PATHS, LIKE THEY DO IN LLVM/CLANG (see Path.inc)
errno = EACCES; // set errno to something significant
fp = NULL; // assume failure until told otherwise
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
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
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
if (nt_filehandle != NULL)
{
intermediary_fd = _open_osfhandle ((intptr_t) nt_filehandle, O_WRONLY); // and convert this handle back into a POSIX file descriptor
if (intermediary_fd != -1)
{
fp = _fdopen (intermediary_fd, "wb"); // now convert this handle to a buffered libc file descriptor
if (fp == NULL)
_close (intermediary_fd); // if we couldn't get a libc file descriptor out of that POSIX file descriptor, close it anyway
}
else
CloseHandle (nt_filehandle); // if we couldn't get a POSIX file descriptor out of that handle, close it anyway
}
if (fp != NULL)
errno = 0; // if file could be opened, clear errno
#else // !defined(_WIN32) || (defined(MAXPATHLEN) && (MAXPATHLEN > 260))
fp
= fopen (file_pathname
, "wb"); // POSIX-compliant system. Good boy.
#endif // defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
if (fp == NULL)
return (0); // on fopen() failure, return an error value (and errno is hopefully set to something meaningful)
}
// and write it at once
if (fwrite (buffer
->bytes
, 1, buffer
->size
, fp
) != buffer
->size
)
{
errno_backup = (errno ? errno : EIO); // preserve errno on errors; if it isn't set, set it to EIO
if ((fp != stdout) && (fp != stderr))
fclose (fp
); // either case, close the file
errno = errno_backup;
return (0); // on failure, return an error value (errno already set to EIO - or something else set by the underlying buffered stream API)
}
if ((fp != stdout) && (fp != stderr))
fclose (fp
); // finished, close file (only if it was not one of the standard streams)
errno = 0; // no error
return (1); // and return SUCCESS
}
uint8_t *Buffer_FindFirst (const buffer_t *buffer, const void *needle, const size_t needle_length)
{
// returns a pointer to the first occurence of needle in buffer haystack, or NULL if not found
size_t byte_index;
if (buffer->size < needle_length)
return (NULL); // consistency check: if buffer is too small for needle, return NULL
else if ((buffer
->size
== needle_length
) && (memcmp (buffer
->bytes
, needle
, buffer
->size
) == 0))
return (buffer->bytes); // special case where needle is exactly as long as buffer
// parse buffer from start to end
for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED!
if (memcmp (&buffer
->bytes
[byte_index
], needle
, needle_length
) == 0)
return (&buffer->bytes[byte_index]); // return the first match we find
return (NULL); // definitely no needle in buffer
}
uint8_t *Buffer_FindFirstCI (const buffer_t *buffer, const char *needle)
{
// returns a pointer to the first occurence of needle in buffer haystack (CASE INSENSITIVELY), or NULL if not found
size_t needle_length;
size_t byte_index;
needle_length
= strlen (needle
); // measure needle length
if (buffer->size < needle_length)
return (NULL); // consistency check: if buffer is too small for needle, return NULL
else if ((buffer
->size
== needle_length
) && (memcmp (buffer
->bytes
, needle
, buffer
->size
) == 0))
return (buffer->bytes); // special case where needle is exactly as long as buffer
// parse buffer from start to end
for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED!
if (strncasecmp ((const char *) &buffer->bytes[byte_index], needle, needle_length) == 0)
return (&buffer->bytes[byte_index]); // return the first match we find
return (NULL); // definitely no needle in buffer
}
uint8_t *Buffer_FindLast (const buffer_t *buffer, const void *needle, const size_t needle_length)
{
// returns a pointer to the last occurence of needle in buffer haystack, or NULL if not found
size_t byte_index;
if (buffer->size < needle_length)
return (NULL); // consistency check: if buffer is too small for needle, return NULL
else if ((buffer
->size
== needle_length
) && (memcmp (buffer
->bytes
, needle
, buffer
->size
) == 0))
return (buffer->bytes); // special case where needle is exactly as long as buffer
// parse buffer from end to start
for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--)
if (memcmp (&buffer
->bytes
[byte_index
], needle
, needle_length
) == 0)
return (&buffer->bytes[byte_index]); // return the last match we find
return (NULL); // definitely no needle in buffer
}
uint8_t *Buffer_FindLastCI (const buffer_t *buffer, const char *needle)
{
// returns a pointer to the last occurence of needle in buffer haystack (CASE INSENSITIVELY), or NULL if not found
size_t needle_length;
size_t byte_index;
needle_length
= strlen (needle
); // measure needle length
if (buffer->size < needle_length)
return (NULL); // consistency check: if buffer is too small for needle, return NULL
else if ((buffer
->size
== needle_length
) && (memcmp (buffer
->bytes
, needle
, buffer
->size
) == 0))
return (buffer->bytes); // special case where needle is exactly as long as buffer
// parse buffer from end to start
for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--)
if (strncasecmp ((const char *) &buffer->bytes[byte_index], needle, needle_length) == 0)
return (&buffer->bytes[byte_index]); // return the last match we find
return (NULL); // definitely no needle in buffer
}
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)
{
// replaces as many occurences of needle with replacement in buffer haystack, reallocating as needed.
// If howmany is strictly positive, replacements stop after howmany replacements are made in the forward direction.
// If howmany is strictly negative, replacements stop after -howmany replacements are made in the REVERSE direction.
// 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).
void *reallocated_ptr;
void *safe_needle;
size_t replacements_made;
size_t replacements_max;
size_t byte_index;
size_t padding;
int realloc_done;
if (needle_length > buffer->size)
{
errno = E2BIG; // consistency check: if buffer is too small for needle, set errno to E2BIG
return (0); // and return an error value
}
if (needle_length == 0)
return (0); // consistency check: if needle is an empty string, return 0 (no replacement made)
// 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) ?
if ((abs (howmany_with_zero_for_all_and_minus_for_backwards
) != 1) && ((uint8_t *) needle
> buffer
->bytes
) && ((uint8_t *) needle
< buffer
->bytes
+ buffer
->size
))
{
safe_needle
= (uint8_t *) malloc (needle_length
); // allocate space for a copy of the needle
if (safe_needle == NULL)
return (0); // on allocation error, return 0 (errno already set to ENOMEM)
memcpy (safe_needle
, needle
, needle_length
); // and copy needle somewhere safe
}
else
safe_needle = (uint8_t *) needle; // else we can use needle directly and spare us an allocation
// see how much we need to replace and in what order
replacements_made = 0;
realloc_done = 0;
if (howmany_with_zero_for_all_and_minus_for_backwards < 0)
{
// backwards direction
replacements_max = -howmany_with_zero_for_all_and_minus_for_backwards;
// parse buffer from end to start
for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--)
{
if (memcmp (&buffer
->bytes
[byte_index
], safe_needle
, needle_length
) != 0)
continue; // if there's no match at this location, skip it
// a replacement should be made here: see if we need to grow the data
if (replacement_length > needle_length)
{
// if so, reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ replacement_length
- needle_length
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
{
replacements_made = 0;
break; // on failure, return an error value (errno is set)
}
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
realloc_done = 1; // and remember that a reallocation was done
}
// move the remaining first if necessary, then put the replacement in place
if (needle_length != replacement_length)
memmove (&buffer
->bytes
[byte_index
+ replacement_length
], &buffer
->bytes
[byte_index
+ needle_length
], buffer
->size
- (byte_index
+ needle_length
));
if (replacement_length > 0)
memcpy (&buffer
->bytes
[byte_index
], replacement
, replacement_length
);
buffer->size += replacement_length - needle_length; // adjust buffer size
replacements_made++; // one more replacement has been made
if ((replacements_max > 0) && (replacements_made >= replacements_max))
break; // if a maximum replacements count was specified, stop here
}
}
else
{
// forward direction
replacements_max = howmany_with_zero_for_all_and_minus_for_backwards;
// parse buffer from start to end
for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED!
{
if (memcmp (&buffer
->bytes
[byte_index
], safe_needle
, needle_length
) != 0)
continue; // if there's no match at this location, skip it
// a replacement should be made here: see if we need to grow the data
if (replacement_length > needle_length)
{
// if so, reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ replacement_length
- needle_length
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
{
replacements_made = 0;
break; // on failure, return an error value (errno is set)
}
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
realloc_done = 1; // and remember that a reallocation was done
}
// move the remaining first (including the null terminator) if necessary, then put the replacement in place
if (needle_length != replacement_length)
memmove (&buffer
->bytes
[byte_index
+ replacement_length
], &buffer
->bytes
[byte_index
+ needle_length
], buffer
->size
- (byte_index
+ needle_length
));
if (replacement_length > 0)
memcpy (&buffer
->bytes
[byte_index
], replacement
, replacement_length
);
buffer->size += replacement_length - needle_length; // adjust buffer size
replacements_made++; // one more replacement has been made
if ((replacements_max > 0) && (replacements_made >= replacements_max))
break; // if a maximum replacements count was specified, stop here
byte_index += replacement_length - 1; // jump over this replacement and proceed to the remaining of the string
}
}
if (safe_needle != needle)
free (safe_needle
); // if we had to allocate space for a safe copy of the needle, free it now
// 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!
if (!realloc_done)
{
// reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
}
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
return (replacements_made); // finished, return the number of replacements made
}
int Buffer_Compare (buffer_t *buffer, const void *data, const size_t data_size)
{
// compares a buffer with some data of data_size length, and return 0 if they match, non-zero if they differ.
// If their size differ, a non-zero value (equivalent to the string "DIFF") is returned.
// If their content differ, the result of memcmp() is returned.
if ((data == buffer->bytes) && (data_size == buffer->size))
return (0); // if both buffers are the same one, return 0
else if (data_size != buffer->size)
return ((int) ((ssize_t) data_size - (ssize_t) buffer->size)); // buffers differ in size, return the shortest first
return (memcmp (buffer
->bytes
, data
, data_size
)); // do a memcmp() on both buffers
}
size_t Buffer_OffsetOf (buffer_t *buffer, const void *something)
{
// returns the absolute offset of the data pointed to by something relatively to the beginning of buffer data
// if something points outside of buffer, returns SIZE_MAX (in which case errno is set to EINVAL)
if (((uint8_t *) something < buffer->bytes) || ((uint8_t *) something > buffer->bytes + buffer->size))
{
errno = EINVAL; // consistency check: if the queried pointer falls outside of the buffer, set errno to EINVAL
return (SIZE_MAX); // and return an error code
}
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
}
int Buffer_TrimLeftUntil (buffer_t *buffer, const uint8_t *trimmable_bytes, const size_t trimmable_count, const int howmany)
{
// removes leading characters from a buffer's data and returns 1 on success, or 0 if a memory
// reallocation error happens (which shouldn't ever since we're reallocating smaller), in which case errno is set to ENOMEM.
// Any character in the trimmable_bytes array is a candidate for elimination.
void *reallocated_ptr;
size_t removed_count;
size_t max_count;
size_t padding;
removed_count = 0; // parse buffer string from the end looking for any of the trimmable bytes
max_count = (howmany == 0 ? buffer->size : howmany); // see how many characters maximum we are allowed to trim
while ((removed_count
< max_count
) && (memchr (trimmable_bytes
, buffer
->bytes
[removed_count
], trimmable_count
) != NULL
))
removed_count++; // count the number of bytes to remove
// did we find something to trim ?
if (removed_count > 0)
{
// if so, shift the buffer data start by the number of bytes to remove
memmove (buffer
->bytes
, &buffer
->bytes
[removed_count
], buffer
->size
- removed_count
);
buffer->size -= removed_count; // adjust the new buffer size
// reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
}
return (1); // and return SUCCESS
}
int Buffer_TrimRightUntil (buffer_t *buffer, const uint8_t *trimmable_bytes, const size_t trimmable_count, const int howmany)
{
// removes trailing characters from a buffer's data and returns 1 on success, or 0 if a memory
// reallocation error happens (which shouldn't ever since we're reallocating smaller), in which case errno is set to ENOMEM.
// Any character in the trimmable_bytes array is a candidate for elimination.
void *reallocated_ptr;
size_t removed_count;
size_t max_count;
size_t padding;
removed_count = 0; // parse buffer string from the end looking for any of the trimmable bytes
max_count = (howmany == 0 ? buffer->size : howmany); // see how many characters maximum we are allowed to trim
while ((buffer
->size
> 0) && (removed_count
< max_count
) && (memchr (trimmable_bytes
, buffer
->bytes
[buffer
->size
- 1], trimmable_count
) != NULL
))
{
removed_count++; // count the number of bytes to remove
buffer->size--; // and decrease buffer size appropriately
}
// did we find something to trim ?
if (removed_count > 0)
{
// if so, reallocate data buffer at the right size
reallocated_ptr
= realloc (buffer
->bytes
, buffer
->size
+ sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
if (reallocated_ptr == NULL)
return (0); // on failure, return an error value (errno already set to ENOMEM)
buffer->bytes = reallocated_ptr; // save pointer to reallocated data
for (padding = 0; padding < sizeof (size_t); padding++)
buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
}
return (1); // and return SUCCESS
}
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)
{
// extract and returns the Nth bit of buffer when split according to separator and return whether
// it succeeded or not (in which case errno is set to EINVAL).
// IMPORTANT: preinitialized_outbuf MUST have been properly initialized before!
ptrdiff_t extractstart_index;
size_t fieldstart_index;
size_t char_index;
size_t part_index;
// handle special case first
if (howmany == 0)
{
if (preinitialized_outbuf->bytes != NULL)
Buffer_Forget (preinitialized_outbuf); // release output buffer if it was previously allocated
Buffer_InitWithByteArray (preinitialized_outbuf, ""); // if we want a zero-length slice, prepare an empty buffer
return (1); // and return SUCCESS
}
// read buffer characters and identify parts on the fly
extractstart_index = -1;
fieldstart_index = 0;
part_index = 0;
for (char_index = 0; char_index < buffer->size; char_index++)
{
// have we NOT found yet the start field and is this it ?
if ((extractstart_index == -1) && (part_index == wanted_part_index))
extractstart_index = fieldstart_index; // if so, remember where it starts
if (should_parse_as_text && (buffer->bytes[char_index] == 0))
break; // when end of text reached, stop searching
else if ((char_index
+ separator_len
<= buffer
->size
) && (memcmp (&buffer
->bytes
[char_index
], separator
, separator_len
) == 0))
{
part_index++; // a separator is here, meaning that one part more was read
if ((howmany > 0) && (part_index == wanted_part_index + howmany))
break; // was it ALL that we were looking for ? if so, stop searching
char_index += separator_len; // skip the separator
fieldstart_index = char_index; // and remember a new part starts here
}
}
// have we found the part we were looking for ?
if (extractstart_index != -1)
{
if (preinitialized_outbuf->bytes != NULL)
Buffer_Forget (preinitialized_outbuf); // release output buffer if it was previously allocated
Buffer_InitWithData (preinitialized_outbuf, &buffer->bytes[extractstart_index], char_index - extractstart_index); // if so, so copy it
return (1); // and return SUCCESS
}
// if we reach here, it means we haven't identified the part we were looking for
errno = EINVAL; // so tell the user the wanted_part_index parameter was invalid
return (0); // and return FAILURE
}