Subversion Repositories QNX 8.QNX8 IFS tool

Rev

Go to most recent revision | Details | 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
343
      memset (&buffer->bytes[buffer->size], 0, write_index - buffer->size); // buffer was upsized: fill the upsized part with zeroes up to write_index
344
   }
345
 
346
   if (data_size > 0)
347
      memcpy (&buffer->bytes[write_index], data, data_size); // write data in place
348
 
349
   if (write_index + data_size > buffer->size)
350
      buffer->size = write_index + data_size; // update data size only if it growed
351
   else
352
   {
353
      // 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!
354
 
355
      // reallocate data buffer at the right size
356
      reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
357
      if (reallocated_ptr == NULL)
358
         return (0); // on failure, return an error value (errno already set to ENOMEM)
359
      buffer->bytes = reallocated_ptr; // save pointer to reallocated data
360
   }
361
   for (padding = 0; padding < sizeof (size_t); padding++)
362
      buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
363
 
364
   return (1); // buffer written successfully, return SUCCESS
365
}
366
 
367
 
368
int Buffer_SubsetFromTo (buffer_t *buffer, const size_t from_index, const size_t to_index)
369
{
370
   // this function shortens the passed buffer by transforming it into its subset ranging
371
   // from from_index (0 meaning beginning of buffer) to to_index (0 *NOT* meaning end of buffer!!! FIXME),
372
   // shortening the buffer as necessary.
373
   // Returns 1 on success, 0 on error (in which case errno is set to E2BIG).
374
 
375
   void *reallocated_ptr;
376
   size_t padding;
377
 
378
   if ((from_index > to_index) || (to_index > buffer->size))
379
   {
380
      errno = E2BIG; // set errno to a significant value
381
      return (0); // consistency check: return FAILURE if we're requesting an out of bounds subset
382
   }
383
 
384
   buffer->size = to_index - from_index; // report the new buffer size
385
 
386
   // should we shift the data ?
387
   if (from_index > 0)
388
      memmove (buffer->bytes, &buffer->bytes[from_index], buffer->size); // shift everything if necessary
389
 
390
   // 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!
391
 
392
   // reallocate data buffer at the right size
393
   reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
394
   if (reallocated_ptr == NULL)
395
      return (0); // on failure, return an error value (errno is set)
396
   buffer->bytes = reallocated_ptr; // save pointer to reallocated data
397
   for (padding = 0; padding < sizeof (size_t); padding++)
398
      buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
399
 
400
   return (1); // buffer subset isolated successfully, return SUCCESS
401
}
402
 
403
 
404
int Buffer_ReadFromFile (buffer_t *buffer, const char *file_pathname)
405
{
406
   // this function copies the contents of file_pathname in a newly allocated data buffer
407
   // and fills in the buffer size accordingly. It is up to the caller to free that buffer.
408
   // 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).
409
 
410
   void *allocated_ptr;
411
   int errno_backup;
412
   size_t filesize;
413
   size_t padding;
414
   FILE *fp; // buffered I/O with FILE * is ___FASTER___ than unbuffered I/O with file descriptors
415
 
416
   // start by reporting a zero length
417
   buffer->bytes = NULL;
418
   buffer->size = 0;
419
 
420
   // open file for binary reading (DO NOT stat() BEFORE, ELSE WE WOULD NEED TO APPLY THE SAME LONG PATHNAME WORKAROUND TWICE)
421
#if defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
422
   // on Windows, work around of the MAX_PATH limitation by using the Unicode APIs and NT-style pathnames
423
   // IMPLEMENTATION NOTE: ON WINDOWS, ALL PATHS ARE CONVERTED TO UTF-16 IN MSVCRT.DLL BEFORE THE ACTUAL SYSTEM CALL,
424
   // SO IT *IS* FASTER TO SYSTEMATICALLY CONVERT ALL PATHS TO NT PATHS, LIKE THEY DO IN LLVM/CLANG (see Path.inc)
425
   errno = EACCES; // set errno to something significant
426
   fp = NULL; // assume failure until told otherwise
427
   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
428
   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
429
   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
430
   if (nt_filehandle != NULL)
431
   {
432
      intermediary_fd = _open_osfhandle ((intptr_t) nt_filehandle, O_RDONLY); // and convert this handle back into a POSIX file descriptor
433
      if (intermediary_fd != -1)
434
      {
435
         fp = _fdopen (intermediary_fd, "rb"); // now convert this handle to a buffered libc file descriptor
436
         if (fp == NULL)
437
            _close (intermediary_fd); // if we couldn't get a libc file descriptor out of that POSIX file descriptor, close it anyway
438
      }
439
      else
440
         CloseHandle (nt_filehandle); // if we couldn't get a POSIX file descriptor out of that handle, close it anyway
441
   }
442
   if (fp != NULL)
443
      errno = 0; // if file could be opened, clear errno
444
#else // !defined(_WIN32) || (defined(MAXPATHLEN) && (MAXPATHLEN > 260))
445
   fp = fopen (file_pathname, "rb"); // POSIX-compliant system. Good boy.
446
#endif // defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
447
   if (fp == NULL)
448
      return (0); // on fopen() failure, return an error value (and errno is hopefully set to something meaningful)
449
 
450
   // measure file size
451
   fseek (fp, 0, SEEK_END);
452
   filesize = ftell (fp);
453
   fseek (fp, 0, SEEK_SET);
454
 
455
   // allocate enough space for it
456
   allocated_ptr = malloc (filesize + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
457
   if (allocated_ptr == NULL)
458
   {
459
      errno_backup = errno; // preserve errno on errors
460
      fclose (fp); // on malloc() error, close file
461
      errno = errno_backup;
462
      return (0); // and return an error value (errno already set to ENOMEM)
463
   }
464
 
465
   buffer->bytes = allocated_ptr; // save pointer to newly allocated data
466
 
467
   // and read it at once
468
   if (fread (buffer->bytes, 1, filesize, fp) != filesize)
469
   {
470
      errno_backup = (errno ? errno : EIO); // preserve errno on errors; if it isn't set, set it to EIO
471
      fclose (fp); // fread() failed, close file
472
      free (buffer->bytes);
473
      errno = errno_backup;
474
      return (0); // on failure, return an error value (errno already set to EIO - or something else set by the underlying buffered stream API)
475
   }
476
 
477
   fclose (fp); // finished, close file
478
   buffer->size = filesize; // report its size
479
   for (padding = 0; padding < sizeof (size_t); padding++)
480
      buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
481
 
482
   return (1); // and return SUCCESS
483
}
484
 
485
 
486
int Buffer_WriteToFile (buffer_t *buffer, const char *file_pathname)
487
{
488
   // this function copies the contents of buffer's data into the file pointed to by
489
   // 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).
490
   // NOTE: "<stdout>" and "<stderr>" pathnames can be used to write to stdout and stderr,
491
   // else all directories up to file_pathname are recursively created if necessary.
492
 
493
   size_t string_index;
494
   size_t length;
495
   int errno_backup;
496
   char *temp_pathname;
497
   char *separator;
498
   char *context;
499
   FILE *fp; // buffered I/O with FILE * is ___FASTER___ than unbuffered I/O with file descriptors
500
 
501
   // do we want to write to the standard output or the standard error?
502
   if (strcmp (file_pathname, "<stdout>") == 0)
503
      fp = stdout;
504
   else if (strcmp (file_pathname, "<stderr>") == 0)
505
      fp = stderr;
506
   else
507
   {
508
      // ensure all the path up to file exists, create directories on the fly if needed
509
      // FIXME: this won't work on Windows with symlinks pointing to nonexistent directories...
510
      length = strlen (file_pathname);
511
      if (length == 0)
512
      {
513
         errno = EINVAL; // if the passed pathname is empty, report invalid parameter
514
         return (0); // and return an error value
515
      }
516
      temp_pathname = strdup (file_pathname); // have a working copy of file_pathname
517
      if (temp_pathname == NULL)
518
         return (0); // on strdup() failure, return an error value (errno already set to ENOMEM)
519
      for (string_index = length - 1; string_index != SIZE_MAX; string_index--) // i.e. loop until it overflows
520
         if ((temp_pathname[string_index] == '/') || (temp_pathname[string_index] == '\\'))
521
            break; // look for the last directory separator and stop as soon as we find it
522
      if (string_index != SIZE_MAX)
523
      {
524
         for (; string_index < length; string_index++)
525
            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
526
         context = NULL;
527
         separator = strtok_r (&temp_pathname[1], "/\\", &context); // for each separator in the remaining string past the first one...
528
         while (separator != NULL)
529
         {
530
            (void) mkdir (temp_pathname, 0755); // create directories recursively (FIXME: THIS IS NOT COMPATIBLE WITH LONG FILENAMES)
531
            temp_pathname[strlen (temp_pathname)] = '/'; // put back the separator
532
            separator = strtok_r (NULL, "/\\", &context); // and look for the next one
533
         }
534
      }
535
      free (temp_pathname); // release our working copy of file_pathname
536
 
537
      // open file for binary writing
538
#if defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
539
      // on Windows, work around of the MAX_PATH limitation by using the Unicode APIs and NT-style pathnames
540
      // IMPLEMENTATION NOTE: ON WINDOWS, ALL PATHS ARE CONVERTED TO UTF-16 IN MSVCRT.DLL BEFORE THE ACTUAL SYSTEM CALL,
541
      // SO IT *IS* FASTER TO SYSTEMATICALLY CONVERT ALL PATHS TO NT PATHS, LIKE THEY DO IN LLVM/CLANG (see Path.inc)
542
      errno = EACCES; // set errno to something significant
543
      fp = NULL; // assume failure until told otherwise
544
      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
545
      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
546
      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
547
      if (nt_filehandle != NULL)
548
      {
549
         intermediary_fd = _open_osfhandle ((intptr_t) nt_filehandle, O_WRONLY); // and convert this handle back into a POSIX file descriptor
550
         if (intermediary_fd != -1)
551
         {
552
            fp = _fdopen (intermediary_fd, "wb"); // now convert this handle to a buffered libc file descriptor
553
            if (fp == NULL)
554
               _close (intermediary_fd); // if we couldn't get a libc file descriptor out of that POSIX file descriptor, close it anyway
555
         }
556
         else
557
            CloseHandle (nt_filehandle); // if we couldn't get a POSIX file descriptor out of that handle, close it anyway
558
      }
559
      if (fp != NULL)
560
         errno = 0; // if file could be opened, clear errno
561
#else // !defined(_WIN32) || (defined(MAXPATHLEN) && (MAXPATHLEN > 260))
562
      fp = fopen (file_pathname, "wb"); // POSIX-compliant system. Good boy.
563
#endif // defined(_WIN32) && (!defined(MAXPATHLEN) || (MAXPATHLEN <= 260))
564
      if (fp == NULL)
565
         return (0); // on fopen() failure, return an error value (and errno is hopefully set to something meaningful)
566
   }
567
 
568
   // and write it at once
569
   if (fwrite (buffer->bytes, 1, buffer->size, fp) != buffer->size)
570
   {
571
      errno_backup = (errno ? errno : EIO); // preserve errno on errors; if it isn't set, set it to EIO
572
      if ((fp != stdout) && (fp != stderr))
573
         fclose (fp); // either case, close the file
574
      errno = errno_backup;
575
      return (0); // on failure, return an error value (errno already set to EIO - or something else set by the underlying buffered stream API)
576
   }
577
 
578
   if ((fp != stdout) && (fp != stderr))
579
      fclose (fp); // finished, close file (only if it was not one of the standard streams)
580
 
581
   errno = 0; // no error
582
   return (1); // and return SUCCESS
583
}
584
 
585
 
586
uint8_t *Buffer_FindFirst (const buffer_t *buffer, const void *needle, const size_t needle_length)
587
{
588
   // returns a pointer to the first occurence of needle in buffer haystack, or NULL if not found
589
 
590
   size_t byte_index;
591
 
592
   if (buffer->size < needle_length)
593
      return (NULL); // consistency check: if buffer is too small for needle, return NULL
594
   else if ((buffer->size == needle_length) && (memcmp (buffer->bytes, needle, buffer->size) == 0))
595
      return (buffer->bytes); // special case where needle is exactly as long as buffer
596
 
597
   // parse buffer from start to end
598
   for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED!
599
      if (memcmp (&buffer->bytes[byte_index], needle, needle_length) == 0)
600
         return (&buffer->bytes[byte_index]); // return the first match we find
601
 
602
   return (NULL); // definitely no needle in buffer
603
}
604
 
605
 
606
uint8_t *Buffer_FindFirstCI (const buffer_t *buffer, const char *needle)
607
{
608
   // returns a pointer to the first occurence of needle in buffer haystack (CASE INSENSITIVELY), or NULL if not found
609
 
610
   size_t needle_length;
611
   size_t byte_index;
612
 
613
   needle_length = strlen (needle); // measure needle length
614
 
615
   if (buffer->size < needle_length)
616
      return (NULL); // consistency check: if buffer is too small for needle, return NULL
617
   else if ((buffer->size == needle_length) && (memcmp (buffer->bytes, needle, buffer->size) == 0))
618
      return (buffer->bytes); // special case where needle is exactly as long as buffer
619
 
620
   // parse buffer from start to end
621
   for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED!
622
      if (strncasecmp ((const char *) &buffer->bytes[byte_index], needle, needle_length) == 0)
623
         return (&buffer->bytes[byte_index]); // return the first match we find
624
 
625
   return (NULL); // definitely no needle in buffer
626
}
627
 
628
 
629
uint8_t *Buffer_FindLast (const buffer_t *buffer, const void *needle, const size_t needle_length)
630
{
631
   // returns a pointer to the last occurence of needle in buffer haystack, or NULL if not found
632
 
633
   size_t byte_index;
634
 
635
   if (buffer->size < needle_length)
636
      return (NULL); // consistency check: if buffer is too small for needle, return NULL
637
   else if ((buffer->size == needle_length) && (memcmp (buffer->bytes, needle, buffer->size) == 0))
638
      return (buffer->bytes); // special case where needle is exactly as long as buffer
639
 
640
   // parse buffer from end to start
641
   for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--)
642
      if (memcmp (&buffer->bytes[byte_index], needle, needle_length) == 0)
643
         return (&buffer->bytes[byte_index]); // return the last match we find
644
 
645
   return (NULL); // definitely no needle in buffer
646
}
647
 
648
 
649
uint8_t *Buffer_FindLastCI (const buffer_t *buffer, const char *needle)
650
{
651
   // returns a pointer to the last occurence of needle in buffer haystack (CASE INSENSITIVELY), or NULL if not found
652
 
653
   size_t needle_length;
654
   size_t byte_index;
655
 
656
   needle_length = strlen (needle); // measure needle length
657
 
658
   if (buffer->size < needle_length)
659
      return (NULL); // consistency check: if buffer is too small for needle, return NULL
660
   else if ((buffer->size == needle_length) && (memcmp (buffer->bytes, needle, buffer->size) == 0))
661
      return (buffer->bytes); // special case where needle is exactly as long as buffer
662
 
663
   // parse buffer from end to start
664
   for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--)
665
      if (strncasecmp ((const char *) &buffer->bytes[byte_index], needle, needle_length) == 0)
666
         return (&buffer->bytes[byte_index]); // return the last match we find
667
 
668
   return (NULL); // definitely no needle in buffer
669
}
670
 
671
 
672
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)
673
{
674
   // replaces as many occurences of needle with replacement in buffer haystack, reallocating as needed.
675
   // If howmany is strictly positive, replacements stop after howmany replacements are made in the forward direction.
676
   // If howmany is strictly negative, replacements stop after -howmany replacements are made in the REVERSE direction.
677
   // 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).
678
 
679
   void *reallocated_ptr;
680
   void *safe_needle;
681
   size_t replacements_made;
682
   size_t replacements_max;
683
   size_t byte_index;
684
   size_t padding;
685
   int realloc_done;
686
 
687
   if (needle_length > buffer->size)
688
   {
689
      errno = E2BIG; // consistency check: if buffer is too small for needle, set errno to E2BIG
690
      return (0); // and return an error value
691
   }
692
 
693
   if (needle_length == 0)
694
      return (0); // consistency check: if needle is an empty string, return 0 (no replacement made)
695
 
696
   // 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) ?
697
   if ((abs (howmany_with_zero_for_all_and_minus_for_backwards) != 1) && ((uint8_t *) needle > buffer->bytes) && ((uint8_t *) needle < buffer->bytes + buffer->size))
698
   {
699
      safe_needle = (uint8_t *) malloc (needle_length); // allocate space for a copy of the needle
700
      if (safe_needle == NULL)
701
         return (0); // on allocation error, return 0 (errno already set to ENOMEM)
702
      memcpy (safe_needle, needle, needle_length); // and copy needle somewhere safe
703
   }
704
   else
705
      safe_needle = (uint8_t *) needle; // else we can use needle directly and spare us an allocation
706
 
707
   // see how much we need to replace and in what order
708
   replacements_made = 0;
709
   realloc_done = 0;
710
   if (howmany_with_zero_for_all_and_minus_for_backwards < 0)
711
   {
712
      // backwards direction
713
      replacements_max = -howmany_with_zero_for_all_and_minus_for_backwards;
714
 
715
      // parse buffer from end to start
716
      for (byte_index = buffer->size - needle_length; byte_index != SIZE_MAX; byte_index--)
717
      {
718
         if (memcmp (&buffer->bytes[byte_index], safe_needle, needle_length) != 0)
719
            continue; // if there's no match at this location, skip it
720
 
721
         // a replacement should be made here: see if we need to grow the data
722
         if (replacement_length > needle_length)
723
         {
724
            // if so, reallocate data buffer at the right size
725
            reallocated_ptr = realloc (buffer->bytes, buffer->size + replacement_length - needle_length + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
726
            if (reallocated_ptr == NULL)
727
            {
728
               replacements_made = 0;
729
               break; // on failure, return an error value (errno is set)
730
            }
731
 
732
            buffer->bytes = reallocated_ptr; // save pointer to reallocated data
733
            realloc_done = 1; // and remember that a reallocation was done
734
         }
735
 
736
         // move the remaining first if necessary, then put the replacement in place
737
         if (needle_length != replacement_length)
738
            memmove (&buffer->bytes[byte_index + replacement_length], &buffer->bytes[byte_index + needle_length], buffer->size - (byte_index + needle_length));
739
         if (replacement_length > 0)
740
            memcpy (&buffer->bytes[byte_index], replacement, replacement_length);
741
         buffer->size += replacement_length - needle_length; // adjust buffer size
742
 
743
         replacements_made++; // one more replacement has been made
744
         if ((replacements_max > 0) && (replacements_made >= replacements_max))
745
            break; // if a maximum replacements count was specified, stop here
746
      }
747
   }
748
   else
749
   {
750
      // forward direction
751
      replacements_max = howmany_with_zero_for_all_and_minus_for_backwards;
752
 
753
      // parse buffer from start to end
754
      for (byte_index = 0; byte_index <= buffer->size - needle_length; byte_index++) // inferior OR EQUAL, meaning last position INCLUDED!
755
      {
756
         if (memcmp (&buffer->bytes[byte_index], safe_needle, needle_length) != 0)
757
            continue; // if there's no match at this location, skip it
758
 
759
         // a replacement should be made here: see if we need to grow the data
760
         if (replacement_length > needle_length)
761
         {
762
            // if so, reallocate data buffer at the right size
763
            reallocated_ptr = realloc (buffer->bytes, buffer->size + replacement_length - needle_length + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
764
            if (reallocated_ptr == NULL)
765
            {
766
               replacements_made = 0;
767
               break; // on failure, return an error value (errno is set)
768
            }
769
 
770
            buffer->bytes = reallocated_ptr; // save pointer to reallocated data
771
            realloc_done = 1; // and remember that a reallocation was done
772
         }
773
 
774
         // move the remaining first (including the null terminator) if necessary, then put the replacement in place
775
         if (needle_length != replacement_length)
776
            memmove (&buffer->bytes[byte_index + replacement_length], &buffer->bytes[byte_index + needle_length], buffer->size - (byte_index + needle_length));
777
         if (replacement_length > 0)
778
            memcpy (&buffer->bytes[byte_index], replacement, replacement_length);
779
         buffer->size += replacement_length - needle_length; // adjust buffer size
780
 
781
         replacements_made++; // one more replacement has been made
782
         if ((replacements_max > 0) && (replacements_made >= replacements_max))
783
            break; // if a maximum replacements count was specified, stop here
784
 
785
         byte_index += replacement_length - 1; // jump over this replacement and proceed to the remaining of the string
786
      }
787
   }
788
 
789
   if (safe_needle != needle)
790
      free (safe_needle); // if we had to allocate space for a safe copy of the needle, free it now
791
 
792
   // 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!
793
   if (!realloc_done)
794
   {
795
      // reallocate data buffer at the right size
796
      reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
797
      if (reallocated_ptr == NULL)
798
         return (0); // on failure, return an error value (errno already set to ENOMEM)
799
      buffer->bytes = reallocated_ptr; // save pointer to reallocated data
800
   }
801
   for (padding = 0; padding < sizeof (size_t); padding++)
802
      buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
803
 
804
   return (replacements_made); // finished, return the number of replacements made
805
}
806
 
807
 
808
int Buffer_Compare (buffer_t *buffer, const void *data, const size_t data_size)
809
{
810
   // compares a buffer with some data of data_size length, and return 0 if they match, non-zero if they differ.
811
   // If their size differ, a non-zero value (equivalent to the string "DIFF") is returned.
812
   // If their content differ, the result of memcmp() is returned.
813
 
814
   if ((data == buffer->bytes) && (data_size == buffer->size))
815
      return (0); // if both buffers are the same one, return 0
816
   else if (data_size != buffer->size)
817
      return ((int) ((ssize_t) data_size - (ssize_t) buffer->size)); // buffers differ in size, return the shortest first
818
 
819
   return (memcmp (buffer->bytes, data, data_size)); // do a memcmp() on both buffers
820
}
821
 
822
 
823
size_t Buffer_OffsetOf (buffer_t *buffer, const void *something)
824
{
825
   // returns the absolute offset of the data pointed to by something relatively to the beginning of buffer data
826
   // if something points outside of buffer, returns SIZE_MAX (in which case errno is set to EINVAL)
827
 
828
   if (((uint8_t *) something < buffer->bytes) || ((uint8_t *) something > buffer->bytes + buffer->size))
829
   {
830
      errno = EINVAL; // consistency check: if the queried pointer falls outside of the buffer, set errno to EINVAL
831
      return (SIZE_MAX); // and return an error code
832
   }
833
 
834
   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
835
}
836
 
837
 
838
int Buffer_TrimLeftUntil (buffer_t *buffer, const uint8_t *trimmable_bytes, const size_t trimmable_count, const int howmany)
839
{
840
   // removes leading characters from a buffer's data and returns 1 on success, or 0 if a memory
841
   // reallocation error happens (which shouldn't ever since we're reallocating smaller), in which case errno is set to ENOMEM.
842
   // Any character in the trimmable_bytes array is a candidate for elimination.
843
 
844
   void *reallocated_ptr;
845
   size_t removed_count;
846
   size_t max_count;
847
   size_t padding;
848
 
849
   removed_count = 0; // parse buffer string from the end looking for any of the trimmable bytes
850
   max_count = (howmany == 0 ? buffer->size : howmany); // see how many characters maximum we are allowed to trim
851
   while ((removed_count < max_count) && (memchr (trimmable_bytes, buffer->bytes[removed_count], trimmable_count) != NULL))
852
      removed_count++; // count the number of bytes to remove
853
 
854
   // did we find something to trim ?
855
   if (removed_count > 0)
856
   {
857
      // if so, shift the buffer data start by the number of bytes to remove
858
      memmove (buffer->bytes, &buffer->bytes[removed_count], buffer->size - removed_count);
859
      buffer->size -= removed_count; // adjust the new buffer size
860
 
861
      // reallocate data buffer at the right size
862
      reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
863
      if (reallocated_ptr == NULL)
864
         return (0); // on failure, return an error value (errno already set to ENOMEM)
865
 
866
      buffer->bytes = reallocated_ptr; // save pointer to reallocated data
867
      for (padding = 0; padding < sizeof (size_t); padding++)
868
         buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
869
   }
870
 
871
   return (1); // and return SUCCESS
872
}
873
 
874
 
875
int Buffer_TrimRightUntil (buffer_t *buffer, const uint8_t *trimmable_bytes, const size_t trimmable_count, const int howmany)
876
{
877
   // removes trailing characters from a buffer's data and returns 1 on success, or 0 if a memory
878
   // reallocation error happens (which shouldn't ever since we're reallocating smaller), in which case errno is set to ENOMEM.
879
   // Any character in the trimmable_bytes array is a candidate for elimination.
880
 
881
   void *reallocated_ptr;
882
   size_t removed_count;
883
   size_t max_count;
884
   size_t padding;
885
 
886
   removed_count = 0; // parse buffer string from the end looking for any of the trimmable bytes
887
   max_count = (howmany == 0 ? buffer->size : howmany); // see how many characters maximum we are allowed to trim
888
   while ((buffer->size > 0) && (removed_count < max_count) && (memchr (trimmable_bytes, buffer->bytes[buffer->size - 1], trimmable_count) != NULL))
889
   {
890
      removed_count++; // count the number of bytes to remove
891
      buffer->size--; // and decrease buffer size appropriately
892
   }
893
 
894
   // did we find something to trim ?
895
   if (removed_count > 0)
896
   {
897
      // if so, reallocate data buffer at the right size
898
      reallocated_ptr = realloc (buffer->bytes, buffer->size + sizeof (size_t)); // always null-terminate buffers, but never report the null terminator
899
      if (reallocated_ptr == NULL)
900
         return (0); // on failure, return an error value (errno already set to ENOMEM)
901
 
902
      buffer->bytes = reallocated_ptr; // save pointer to reallocated data
903
      for (padding = 0; padding < sizeof (size_t); padding++)
904
         buffer->bytes[buffer->size + padding] = 0; // always null-terminate buffers, silently
905
   }
906
 
907
   return (1); // and return SUCCESS
908
}
909
 
910
 
911
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)
912
{
913
   // extract and returns the Nth bit of buffer when split according to separator and return whether
914
   // it succeeded or not (in which case errno is set to EINVAL).
915
   // IMPORTANT: preinitialized_outbuf MUST have been properly initialized before!
916
 
917
   ptrdiff_t extractstart_index;
918
   size_t fieldstart_index;
919
   size_t char_index;
920
   size_t part_index;
921
 
922
   // handle special case first
923
   if (howmany == 0)
924
   {
925
      if (preinitialized_outbuf->bytes != NULL)
926
         Buffer_Forget (preinitialized_outbuf); // release output buffer if it was previously allocated
927
      Buffer_InitWithByteArray (preinitialized_outbuf, ""); // if we want a zero-length slice, prepare an empty buffer
928
      return (1); // and return SUCCESS
929
   }
930
 
931
   // read buffer characters and identify parts on the fly
932
   extractstart_index = -1;
933
   fieldstart_index = 0;
934
   part_index = 0;
935
   for (char_index = 0; char_index < buffer->size; char_index++)
936
   {
937
      // have we NOT found yet the start field and is this it ?
938
      if ((extractstart_index == -1) && (part_index == wanted_part_index))
939
         extractstart_index = fieldstart_index; // if so, remember where it starts
940
 
941
      if (should_parse_as_text && (buffer->bytes[char_index] == 0))
942
         break; // when end of text reached, stop searching
943
      else if ((char_index + separator_len <= buffer->size) && (memcmp (&buffer->bytes[char_index], separator, separator_len) == 0))
944
      {
945
         part_index++; // a separator is here, meaning that one part more was read
946
 
947
         if ((howmany > 0) && (part_index == wanted_part_index + howmany))
948
            break; // was it ALL that we were looking for ? if so, stop searching
949
 
950
         char_index += separator_len; // skip the separator
951
         fieldstart_index = char_index; // and remember a new part starts here
952
      }
953
   }
954
 
955
   // have we found the part we were looking for ?
956
   if (extractstart_index != -1)
957
   {
958
      if (preinitialized_outbuf->bytes != NULL)
959
         Buffer_Forget (preinitialized_outbuf); // release output buffer if it was previously allocated
960
      Buffer_InitWithData (preinitialized_outbuf, &buffer->bytes[extractstart_index], char_index - extractstart_index); // if so, so copy it
961
      return (1); // and return SUCCESS
962
   }
963
 
964
   // if we reach here, it means we haven't identified the part we were looking for
965
   errno = EINVAL; // so tell the user the wanted_part_index parameter was invalid
966
   return (0); // and return FAILURE
967
}