posix.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief Intended to implement missing or buggy POSIX functionality of OS
4  *
5  * Copyright (c) 2012-2024 by the developers. See the LICENSE file for details.
6  */
7 
8 
9 /* ========================================================================== */
10 /* Include headers */
11 
12 #include "posix.h" /* Include this first because of feature test macros */
13 
14 #include "main.h"
15 
16 
17 /* ========================================================================== */
18 /*! \defgroup POSIX POSIX: Portable Operating System Interface
19  *
20  * This subsystem should hold away operating system issues from the rest of the
21  * program. If a C source file requires something that is not ISO C90, it should
22  * include "posix.h" as the first header file and this module should take care
23  * to provide the required functionality as specified by SUSv4.
24  * This allows to write cleaner code (that normally would only be accepted by
25  * much more recent operating systems) and nevertheless run it on old machines.
26  *
27  * This module is based on the POSIX.1-1990, POSIX.1b-1993, POSIX.1c-1996 and
28  * BSD socket APIs. This means it expects that the operating system provides
29  * these APIs.
30  * Workarounds for operating system bugs should be implemented here.
31  *
32  * Version numbers for Single Unix Specification (X/Open XSI extension):
33  * - SUS (Issue 4.2)
34  * - SUSv2 (Issue 5)
35  * - SUSv3 (Issue 6)
36  * - SUSv4 (Issue 7)
37  * - SUSv5 (Issue 8)
38  *
39  * This module can use the XSI extension for SUSv2, SUSv3, SUSv4 or SUSv5
40  * respectively.
41  * POSIX Networking Services (XNS) are used instead of the BSD socket API for
42  * SUSv2 and POSIX.1-2001 (or later versions).
43  *
44  * \attention
45  * The random number generation functions provided by this module may use weak
46  * (or even trivial) algorithms and are not suited for cryptographic tasks!
47  *
48  * \attention
49  * Note that POSIX.1-1990 has no C++ binding, therefore a C++ file is not
50  * allowed to include "posix.h".
51  *
52  * The namespaces reserved for the XNS module of the OS are defined by SUSv2:
53  * <br>
54  * http://pubs.opengroup.org/onlinepubs/007908799/xns/namespace.html
55  * <br>
56  * Since POSIX.1-2024 the whole namespaces with "posix_" and "POSIX_" prefix are
57  * reserved:
58  * <br>
59  * https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/V1_chap13.html
60  * <br>
61  * For this module the "api_posix_" and "API_POSIX_" namespaces are used.
62  * All elements provided by this module have such prefixes.
63  *
64  * Functions not defined in POSIX.1-1990, POSIX.1b-1993, POSIX.1c-1996 or SUSv2,
65  * but defined in a later version should be implemented here in a POSIX
66  * conformant way but with an "api_posix_" prefix.
67  * For partial implementations the parts that are implemented should be POSIX
68  * or SUS conformant respectively.
69  *
70  * Currently the following functions are completely implemented:
71  * - \c api_posix_freeaddrinfo()
72  * - \c api_posix_fsync()
73  * - \c api_posix_getline()
74  * - \c api_posix_inet_pton()
75  * - \c api_posix_poll()
76  * - \c api_posix_random()
77  * - \c api_posix_scandir()
78  * - \c api_posix_srandom()
79  * - \c api_posix_mkstemp()
80  * - \c api_posix_gai_strerror()
81  *
82  * Currently the following functions are only partially implemented:
83  * - \c api_posix_getaddrinfo()
84  * - \c api_posix_inet_addr()
85  * - \c api_posix_snprintf()
86  * - \c api_posix_newlocale()
87  * - \c api_posix_freelocale()
88  * - \c api_posix_strcasecmp_l()
89  * - \c api_posix_strncasecmp_l()
90  *
91  * \attention
92  * \c struct \c sockaddr_storage is broken on AIX if POSIX or SUS mode is used:
93  * - The structure is not exposed at all if '_ALL_SOURCE' is not defined
94  * - The member 'ss_family' required by SUSv2 and POSIX.1-2001 is missing
95  * - At least on AIX 5.3 the structure is not large enough
96  * <br>
97  * Using this structure should be avoided. Using '_ALL_SOURCE' as workaround has
98  * unwanted side effects and would solve only the first problem.
99  */
100 
101 
102 /* ========================================================================== */
103 /* Constants */
104 
105 /*! \brief Message prefix for POSIX module */
106 #define MAIN_ERR_PREFIX "POSIX: "
107 
108 
109 /* ========================================================================== */
110 /* Variables */
111 
112 #if CFG_USE_POSIX_API >= 200809
113 /* Dummy variable to avoid warnings like "empty translation unit" */
114 extern int api_posix_dummy;
115 int api_posix_dummy = 0;
116 #endif /* CFG_USE_POSIX_API >= 200809 */
117 
118 
119 /* ========================================================================== */
120 /* Memory managment debugging
121  *
122  * This is a simple wrapper for the default memory manager that can detect
123  * invalid free() operations. It is always disabled by default.
124  *
125  * Only the parts of the program that use the memory manager API with "posix_"
126  * prefix are checked.
127  */
128 
129 #if CFG_USE_MEMDEBUG
130 # define MEMDEBUG_ARRAY_SIZE (size_t) 100000
131 static api_posix_pthread_mutex_t memdebug_mutex
132  = API_POSIX_PTHREAD_MUTEX_INITIALIZER;
133 static void* memdebug_array[MEMDEBUG_ARRAY_SIZE];
134 static size_t memdebug_size[MEMDEBUG_ARRAY_SIZE];
135 static size_t memdebug_num = 0;
136 
137 /* Replacement for 'malloc()' */
138 void* api_posix_malloc(size_t len)
139 {
140  void* p = NULL;
141 
142  if(!api_posix_pthread_mutex_lock(&memdebug_mutex))
143  {
144  if(MEMDEBUG_ARRAY_SIZE <= memdebug_num + (size_t) 1)
145  {
146  PRINT_ERROR("malloc(): Debug array overflow");
147  return(NULL);
148  }
149  else
150  {
151  p = malloc(len);
152  printf("%s: %sMEMDBG: A: %p\n", CFG_NAME, MAIN_ERR_PREFIX, p);
153  if(p)
154  {
155  memdebug_array[memdebug_num] = p;
156  memdebug_size[memdebug_num] = len;
157  ++memdebug_num;
158  }
159  }
160  api_posix_pthread_mutex_unlock(&memdebug_mutex);
161  }
162 
163  return(p);
164 }
165 
166 
167 /* Replacement for 'free()' */
168 void api_posix_free(void* p)
169 {
170  size_t i, ii;
171 
172  if(!api_posix_pthread_mutex_lock(&memdebug_mutex))
173  {
174  if(NULL != p)
175  {
176  printf("%s: %sMEMDBG: F: %p\n", CFG_NAME, MAIN_ERR_PREFIX, p);
177  for(i = 0; i < memdebug_num; ++i)
178  {
179  if(memdebug_array[i] == p) { break; }
180  }
181  if(i >= memdebug_num)
182  {
183  PRINT_ERROR("free(): Invalid address (thread frozen)");
184  while(1);
185  }
186  else
187  {
188  for(ii = i; ii < memdebug_num; ++ii)
189  {
190  memdebug_array[ii] = memdebug_array[ii + 1];
191  memdebug_size[ii] = memdebug_size[ii + 1];
192  }
193  --memdebug_num;
194  }
195  }
196  api_posix_pthread_mutex_unlock(&memdebug_mutex);
197  }
198 
199  return(free(p));
200 }
201 
202 
203 /* Replacement for 'realloc()' */
204 void* api_posix_realloc(void* p, size_t len)
205 {
206  void* q = api_posix_malloc(len);
207  size_t i;
208  size_t oldlen;
209 
210  if(!api_posix_pthread_mutex_lock(&memdebug_mutex))
211  {
212  if(NULL != p)
213  {
214  printf("%s: %sMEMDBG: R: %p => %p\n", CFG_NAME, MAIN_ERR_PREFIX, p, q);
215  for(i = 0; i < memdebug_num; ++i)
216  {
217  if(memdebug_array[i] == p) { break; }
218  }
219  if(i >= memdebug_num)
220  {
221  PRINT_ERROR("realloc(): Invalid address (thread frozen)");
222  while(1);
223  }
224  else if(NULL != q)
225  {
226  /* Never copy more data than contained in the old memory block */
227  oldlen = memdebug_size[i];
228  if(len > oldlen) { len = oldlen; }
229  memcpy(q, p, len);
230  }
231  }
232  api_posix_pthread_mutex_unlock(&memdebug_mutex);
233  }
234 
235  /* Check for error */
236  if(NULL == q) { PRINT_ERROR("realloc(): Failed"); }
237  else { api_posix_free(p); }
238 
239  return(q);
240 }
241 #endif /* CFG_USE_MEMDEBUG */
242 
243 
244 /* ========================================================================== */
245 /* Random numbers
246  *
247  * \attention
248  * The POSIX RNG is not suitable for cryptographic purposes!
249  */
250 
251 #if !CFG_USE_XSI
252 static api_posix_pthread_mutex_t random_mutex
253  = API_POSIX_PTHREAD_MUTEX_INITIALIZER;
254 static long int random_state = 1;
255 
256 void api_posix_srandom(unsigned int seed)
257 {
258  if(!api_posix_pthread_mutex_lock(&random_mutex))
259  {
260  random_state = (long int) (seed * 2U);
261  api_posix_pthread_mutex_unlock(&random_mutex);
262  }
263 }
264 
265 
266 long int api_posix_random(void)
267 {
268  long int res = 0;
269 
270  if(!api_posix_pthread_mutex_lock(&random_mutex))
271  {
272  res = random_state;
273 
274  /*
275  * Trivial increment implementation so that two sequential numbers can
276  * never have the same value.
277  */
278  if(API_POSIX_LONG_MAX > random_state) { ++random_state; }
279  else { random_state = 0; }
280  api_posix_pthread_mutex_unlock(&random_mutex);
281  }
282 
283  return(res);
284 }
285 #endif /* !CFG_USE_XSI */
286 
287 
288 /* ========================================================================== */
289 /* File & directory handling */
290 
291 #if !CFG_USE_FSC
292 /*
293  * 'fsync()' is part of the POSIX.1-2001 FSC option and X/Open XSI extension.
294  */
295 int api_posix_fsync(int fd)
296 {
297  /* NOP implementation is allowed */
298  return(0);
299 }
300 #endif /* !CFG_USE_FSC */
301 
302 
303 #if CFG_USE_POSIX_API < 200809
304 /*
305  * This implementation of 'scandir()' should be POSIX.1-2008 conformant.
306  *
307  * It is required that 'SSIZE_MAX' is at least 'INT_MAX' (must be checked by
308  * build system).
309  *
310  * Note:
311  * In theory, a system that provide threads should also provide 'readdir_r()',
312  * a thread safe version of 'readdir()'. In reality this is not always the case.
313  * In addition there may be a race condition that can lead to a buffer overflow:
314  * http://womble.decadent.org.uk/readdir_r-advisory.html
315  * Therefore we use our own function 'scandir_readentry()' to get the directory
316  * entries.
317  */
318 static api_posix_pthread_mutex_t scandir_mutex
319  = API_POSIX_PTHREAD_MUTEX_INITIALIZER;
320 static int scandir_readentry(API_POSIX_DIR* dirp,
321  api_posix_struct_dirent** entryp,
322  size_t* len)
323 {
324  int result = -1;
325  api_posix_struct_dirent* e;
326 
327  if(!api_posix_pthread_mutex_lock(&scandir_mutex))
328  {
329  /* Lock UI mutex */
330  if(ts_lock_ui()) { PRINT_ERROR("Locking UI mutex failed"); }
331  else
332  {
333  api_posix_errno = 0;
334  e = api_posix_readdir(dirp);
335  if(NULL == e)
336  {
337  if(!errno)
338  {
339  /* No more entries in directory */
340  *entryp = NULL;
341  *len = 0;
342  result = 0;
343  }
344  }
345  else
346  {
347  /* Entry found, allocate local buffer */
348  *len = offsetof(api_posix_struct_dirent, d_name) + strlen(e->d_name)
349  + (size_t) 1;
350  *entryp = (api_posix_struct_dirent *) api_posix_malloc(*len);
351  if(NULL != *entryp)
352  {
353  memcpy((void*) *entryp, (void*) e, *len);
354  /* Force NUL termination at end of buffer */
355  ((char *) *entryp)[*len - (size_t) 1] = 0;
356  result = 0;
357  }
358  }
359  /* Unlock UI mutex */
360  if(ts_unlock_ui()) { PRINT_ERROR("Unlocking UI mutex failed"); }
361  }
362  /*
363  * In a multithreading environment the systems dirent buffer may be shared
364  * between all threads. Therefore the mutex must stay locked until we have
365  * copied the data to our thread local buffer.
366  */
367  api_posix_pthread_mutex_unlock(&scandir_mutex);
368  }
369 
370  return(result);
371 }
372 #define SCANDIR_ENTRIES_MIN (size_t) 32
373 int api_posix_scandir(const char* dir, api_posix_struct_dirent*** namelist,
374  int (*sel)(api_posix_struct_dirent*),
375  int (*compar)(const api_posix_struct_dirent**,
376  const api_posix_struct_dirent**))
377 {
378  int result = -1;
379  API_POSIX_DIR* dirp;
380  size_t len, num = 0;
381  size_t max = SCANDIR_ENTRIES_MIN;
382  api_posix_struct_dirent* entryp = NULL;
383  api_posix_struct_dirent** entries;
384  api_posix_struct_dirent** p;
385 
386  entries
387  = (api_posix_struct_dirent**) api_posix_malloc(sizeof(*entries) * max);
388  if(NULL != entries)
389  {
390  /* Open directory 'dir' (and verify that it really is a directory) */
391  dirp = api_posix_opendir(dir);
392  if(NULL != dirp)
393  {
394  /* Read next directory entry */
395  while (!scandir_readentry(dirp, &entryp, &len))
396  {
397  if(NULL == entryp)
398  {
399  /* EOD => Return number of directory entries */
400  result = (int) num;
401  break;
402  }
403  /* Apply select function if there is one provided */
404  if(NULL != sel) { if(!sel(entryp)) continue; }
405  entries[num++] = entryp;
406  if(num >= max)
407  {
408  /* Allocate exponentially increasing sized memory chunks */
409  if(API_POSIX_INT_MAX / 2 >= (int) max) { max *= (size_t) 2; }
410  else
411  {
412  api_posix_errno = API_POSIX_ENOMEM;
413  break;
414  }
415  p = (api_posix_struct_dirent**) api_posix_realloc((void*) entries,
416  sizeof(*entries) * max);
417  if(NULL != p) { entries = p; }
418  else break;
419  }
420  }
421  api_posix_closedir(dirp);
422  /*
423  * A standard conformant 'closedir()' is allowed to fail with 'EINTR',
424  * but the state of the directory structure is undefined in this case.
425  * Therefore we ignore the return value because we can't call
426  * 'closedir()' again and must hope that the system has released all
427  * ressources.
428  */
429  }
430  /* Sort entries in array if there is a compare function provided */
431  if(NULL != compar)
432  {
433  qsort((void*) entries, num, sizeof(*entries),
434  (int (*)(const void*, const void*)) compar);
435  }
436  *namelist = entries;
437  }
438 
439  /* Check for error */
440  if(-1 == result)
441  {
442  /* Free all memory we have allocated */
443  while(num--) { api_posix_free((void*) entries[num]); }
444  api_posix_free((void*) entries);
445  }
446 
447  return(result);
448 }
449 #endif /* CFG_USE_POSIX_API < 200809 */
450 
451 
452 #if CFG_USE_POSIX_API < 200809 && !CFG_USE_XSI
453 static unsigned long int mkstemp_state = 0;
454 static api_posix_pthread_mutex_t mkstemp_mutex
455  = API_POSIX_PTHREAD_MUTEX_INITIALIZER;
456 int api_posix_mkstemp(char* template)
457 {
458  const int oflag = API_POSIX_O_RDWR | API_POSIX_O_CREAT | API_POSIX_O_EXCL;
459  const api_posix_mode_t mode = API_POSIX_S_IRUSR | API_POSIX_S_IWUSR;
460  int res = -1;
461  unsigned long int unique;
462  int error = 0;
463  size_t pos;
464  char uni[7];
465  int rv;
466 
467  if(!api_posix_pthread_mutex_lock(&mkstemp_mutex))
468  {
469  if(999999UL > mkstemp_state) { ++mkstemp_state; }
470  else { mkstemp_state = 0; }
471  unique = mkstemp_state;
472  api_posix_pthread_mutex_unlock(&mkstemp_mutex);
473 
474  /* Check template */
475  pos = strlen(template);
476  if((size_t) 6 > pos) { error = 1; }
477  else
478  {
479  pos -= (size_t) 6;
480  if(strcmp("XXXXXX", &template[pos])) { error = 1; }
481  }
482  if(error) { api_posix_errno = API_POSIX_EINVAL; }
483  else
484  {
485  rv = api_posix_snprintf(uni, (size_t) 7, "%06lu", unique);
486  if(6 == rv)
487  {
488  strncpy(&template[pos], uni, (size_t) 7);
489  res = api_posix_open(template, oflag, mode);
490  }
491  }
492  }
493 
494  return(res);
495 }
496 #endif /* CFG_USE_POSIX_API < 200809 && CFG_USE_XSI */
497 
498 
499 /* ========================================================================== */
500 /* NLS */
501 
502 #if CFG_USE_POSIX_API < 200809
503 /*
504  * Only the following parameter set is supported:
505  * category_mask API_POSIX_LC_CTYPE_MASK
506  * locale "POSIX"
507  */
508 api_posix_locale_t api_posix_newlocale(int category_mask, const char* locale,
509  api_posix_locale_t base)
510 {
511  api_posix_locale_t res = 0;
512 
513  (void) base;
514 
515  if(API_POSIX_LC_CTYPE_MASK != category_mask)
516  {
517  api_posix_errno = API_POSIX_EINVAL;
518  }
519  else
520  {
521  if(strcmp(locale, "POSIX")) { api_posix_errno = API_POSIX_ENOENT; }
522  else { res = 1; }
523  }
524 
525  return(res);
526 }
527 
528 
529 void api_posix_freelocale(api_posix_locale_t locobj)
530 {
531  /* NOP (this implementation doesn't allocate memory for locale objects) */
532  (void) locobj;
533 
534  return;
535 }
536 
537 
538 /* 'locale' must always have its 'LC_CTYPE' element set to "POSIX" */
539 int api_posix_strncasecmp_l(const char* s1, const char* s2, size_t n,
540  api_posix_locale_t locale)
541 {
542  int res = 0;
543  size_t i;
544  int c1;
545  int c2;
546 
547  (void) locale;
548 
549  /* Compare in lower case */
550  for(i = 0; i < n; ++i)
551  {
552  c1 = (int) (unsigned char) s1[i];
553  c2 = (int) (unsigned char) s2[i];
554  /* Convert capital letters to lower case */
555  if(65 <= c1 && 90 >= c1) { c1 = (c1 - 65) + 97; }
556  if(65 <= c2 && 90 >= c2) { c2 = (c2 - 65) + 97; }
557  /* Check for end of strings and difference */
558  res = c1 - c2;
559  if(!c1 || !c2 || res) { break; }
560  }
561 
562  return(res);
563 }
564 
565 
566 int api_posix_strcasecmp_l(const char* s1, const char* s2,
567  api_posix_locale_t locale)
568 {
569  return(api_posix_strncasecmp_l(s1, s2, API_POSIX_SIZE_MAX, locale));
570 }
571 #endif /* CFG_USE_POSIX_API < 200809 */
572 
573 
574 /* ========================================================================== */
575 /* Standard I/O */
576 
577 #if CFG_USE_POSIX_API < 200112
578 /*
579  * Attention:
580  * The semantics for parameters s, n and return value in SUSv2 are different
581  * than in C99, POSIX.1-2001 and SUSv3!
582  *
583  * To avoid additional bloat, this implementation of 'snprintf()' contains
584  * only the subset of features that are required. What is present should be
585  * POSIX.1-2008 conformant.
586  *
587  * This function is thread safe.
588  *
589  * Supported conversions in the format string \e f :
590  * %% Literal percent character
591  * %s char* (string with NUL termination)
592  * %d signed int
593  * %u unsigned int
594  * %x unsigned int to hexadecimal (with small letters)
595  * %X unsigned int to hexadecimal (with capital letters)
596  *
597  * The integer conversions support the following modifiers:
598  * 0 Pad fields with minimum width using zeros instead of spaces
599  * [1-9] Minimum field width
600  * l Convert a long argument (signed long int, unsigned long int)
601  *
602  * If the return value is larger than \e n , the result was truncated to fit
603  * into the buffer pointed to by \e s .
604  *
605  * \attention
606  * The result string is only valid for locales that use the US-ASCII character
607  * set (or a superset of it).
608  *
609  * \note
610  * As a fallback, this function should be optimized for correctness, not speed.
611  * Calling 'realloc()' on the buffer for every integer conversion is very
612  * inefficient, but it's a safe solution for the problem how much buffer space
613  * is required for a number specified with a size of "at least" something.
614  */
615 # define SNPRINTF_RESET(c) \
616 { \
617  cn = 0; \
618  percent = c; \
619  mod_l = 0; \
620  mod_z = 0; \
621  mod_w = 0; \
622  lz = 1; \
623  ds = 0; \
624  conv = c; \
625 }
626 int api_posix_snprintf(char* s, size_t n, const char* f, ...)
627 {
628  va_list ap; /* Object for argument list handling */
629  int res; /* Result */
630  int err = 0; /* Error flag */
631  int err_no = API_POSIX_EINVAL; /* Error code */
632 
633  int conv; /* Conversion in progress flag */
634  int percent; /* Literal percent conversion flag */
635  int mod_l; /* Long modifier detected flag */
636  int mod_z; /* Leading zero modifier detected flag */
637  int mod_w; /* Minimum field width modifier if nonzero */
638  int hex_capital;
639 
640  const size_t fn = strlen(f); /* Length of format string */
641  size_t fi = 0; /* Index in format string */
642 
643  size_t sn = 0; /* Length of target string (not truncated) */
644  size_t si = 0; /* Index in target buffer */
645 
646  const char* c_s = ""; /* Pointer to string with conv. result */
647  size_t cn; /* Length of conversion result */
648  size_t ci; /* Index in conversion result */
649 
650  int lz; /* Leading zero flag */
651  int ds; /* Deferred sign flag */
652  size_t exp; /* Exponent */
653 
654  long int d; /* Parameter for signed int conversion */
655  long int d_p10 = 0; /* Greatest power of 10 */
656  long int d_lim; /* Limiting value */
657 
658  unsigned long int u; /* Parameter for unsigned int conversion */
659  unsigned long int u_p10 = 0; /* Greatest power of 10 */
660  unsigned long int u_lim; /* Limiting value */
661 
662  unsigned long int u_p16 = 0; /* Greatest power of 16 */
663  unsigned long int u_dig; /* Digit value */
664 
665  char b1[2]; /* Buffer for single character string */
666  char* b = NULL; /* Buffer for conversion results */
667  size_t bn = 0; /* Length of conversion buffer */
668 
669  long int i;
670  unsigned long int ui;
671  char cc;
672 
673  /* Init state */
674  SNPRINTF_RESET(0);
675  va_start(ap, f);
676 
677 #if 0 /* SUSv2 semantics */
678  /* Do not accept zero buffer length */
679  if(!n)
680  {
681  /* SUSv2 do not allow this, but C99 do => Reject it */
682  PRINT_ERROR("snprintf(): Calling with zero buffer size is not portable");
683  err_no = API_POSIX_EINVAL;
684  err = -1;
685  }
686 #else /* C99/POSIX.1-2001/SUSv3 semantics */
687  if (n && NULL == s)
688  {
689  PRINT_ERROR("snprintf(): NULL for buffer, but nonzero buffer size");
690  err_no = API_POSIX_EINVAL;
691  err = -1;
692  }
693 #endif
694  /* Check for size limit */
695  else if((unsigned int) API_POSIX_INT_MAX < n)
696  {
697  err_no = API_POSIX_EOVERFLOW;
698  err = -1;
699  }
700  else
701  {
702  /* Init output to empty string after it was checked that n is nonzero */
703  /* Attention: C99 allows (NULL == s) and (n == 0) */
704  if(n) { s[0] = 0; }
705  /* Parse format string */
706  while(fn > fi)
707  {
708  if (!conv)
709  {
710  /* Copy anything that is not a conversion specification literally */
711  c_s = &f[fi];
712  cn = strcspn(&f[fi], "%");
713  fi += cn;
714  conv = 1;
715  percent = 0;
716  }
717  else
718  {
719  /* Conversion */
720  cc = f[fi++];
721  hex_capital = 0;
722  switch(cc)
723  {
724  case '%': /* Conversion marker */
725  {
726  if(!percent)
727  {
728  /* Reset state for new conversion */
729  SNPRINTF_RESET(1);
730  }
731  else
732  {
733  /* Exec literal '%' conversion */
734  conv = 0;
735  b1[0] = '%';
736  b1[1] = 0;
737  c_s = b1;
738  cn = 1;
739  }
740  break;
741  }
742  case 'l': /* Long modifier */
743  {
744  mod_l = 1;
745  break;
746  }
747  case '0': /* Leading zero modifier */
748  {
749  mod_z = 1;
750  break;
751  }
752  case '1': /* Minimum field width modifier */
753  case '2': /* Minimum field width modifier */
754  case '3': /* Minimum field width modifier */
755  case '4': /* Minimum field width modifier */
756  case '5': /* Minimum field width modifier */
757  case '6': /* Minimum field width modifier */
758  case '7': /* Minimum field width modifier */
759  case '8': /* Minimum field width modifier */
760  case '9': /* Minimum field width modifier */
761  {
762  mod_w = (int) cc - 48;
763  break;
764  }
765  case 'd': /* Exec signed integer conversion */
766  {
767  conv = 0;
768  /* Calculate greatest power of 10 for signed integer */
769  if(mod_l) { d_lim = API_POSIX_LONG_MAX; }
770  else { d_lim = (long int) API_POSIX_INT_MAX; }
771  d_p10 = 10000L; exp = 4;
772  while(1)
773  {
774  if(10 > d_lim / d_p10) { break; }
775  d_p10 *= 10; ++exp;
776  }
777  /* Maximum required size: Sign + exponent + 1 + termination */
778  bn = exp + (size_t) 3;
779  /* Allocate buffer for conversion result */
780  b = (char*) api_posix_realloc((void*) b, bn);
781  if (NULL == b)
782  {
783  err_no = API_POSIX_ENOMEM; err = 1;
784  break;
785  }
786  /* Get signed value from stack */
787  if(mod_l) { d = va_arg(ap, long int); }
788  else { d = (long int) va_arg(ap, int); }
789  ci = 0;
790  /*
791  * Process sign
792  * Positive values are negated because on machines with 2's
793  * complement arithmetic the negative range is larger.
794  */
795  if(0L > d)
796  {
797  if(!mod_w) { b[ci++] = '-'; }
798  else
799  {
800  --mod_w;
801  if (mod_z) { b[ci++] = '-'; }
802  else { ds = 1; }
803  }
804  }
805  else { d = -d; }
806  /* Process digits */
807  i = -d_p10;
808  while(-1L >= i)
809  {
810  if(d <= i)
811  {
812  lz = 0;
813  if(ds) { b[ci++] = '-'; ds = 0; }
814  b[ci++] = (char) (48L + d / i);
815  d -= d / i * i;
816  }
817  else if(lz)
818  {
819  if(!d && -1L == i) { b[ci++] = '0'; }
820  else
821  {
822  /* Check minimum field width */
823  if(mod_w > exp)
824  {
825  if(!mod_z) { b[ci++] = ' '; }
826  else { b[ci++] = '0'; }
827  }
828  }
829  }
830  else { b[ci++] = '0'; }
831  i /= 10L; --exp;
832  if(ci >= bn) { break; }
833  }
834  /* Sanity check */
835  if(ci >= bn) { si = API_POSIX_SIZE_MAX; err = 1; break; }
836  /* Terminate conversion result */
837  b[ci] = 0;
838  c_s = b;
839  cn = ci;
840  break;
841  }
842  case 'u': /* Exec unsigned integer conversion */
843  {
844  conv = 0;
845  /* Calculate greatest power of 10 for unsigned integer */
846  if(mod_l) { u_lim = API_POSIX_ULONG_MAX; }
847  else { u_lim = (long int) API_POSIX_UINT_MAX; }
848  u_p10 = 10000UL; exp = 4;
849  while(1)
850  {
851  if(10 > u_lim / u_p10) { break; }
852  u_p10 *= 10; ++exp;
853  }
854  /* Maximum required size: exponent + 1 + termination */
855  bn = exp + (size_t) 2;
856  /* Allocate buffer for conversion result */
857  b = (char*) api_posix_realloc((void*) b, bn);
858  if (NULL == b)
859  {
860  err_no = API_POSIX_ENOMEM; err = 1;
861  break;
862  }
863  /* Get unsigned value from stack */
864  if(mod_l) { u = va_arg(ap, unsigned long int); }
865  else { u = (unsigned long int) va_arg(ap, unsigned int); }
866  ci = 0;
867  /* Process digits */
868  ui = u_p10;
869  while(1UL <= ui)
870  {
871  if(u >= ui)
872  {
873  lz = 0;
874  b[ci++] = (char) (48UL + u / ui);
875  u -= u / ui * ui;
876  }
877  else if(lz)
878  {
879  if(!u && 1UL == ui) { b[ci++] = '0'; }
880  else
881  {
882  /* Check minimum field width */
883  if(mod_w > exp)
884  {
885  if(!mod_z) { b[ci++] = ' '; }
886  else { b[ci++] = '0'; }
887  }
888  }
889  }
890  else { b[ci++] = '0'; }
891  ui /= 10UL; --exp;
892  if(ci >= bn) { break; }
893  }
894  /* Sanity check */
895  if(ci >= bn) { si = API_POSIX_SIZE_MAX; err = 1; break; }
896  /* Terminate conversion result */
897  b[ci] = 0;
898  c_s = b;
899  cn = ci;
900  break;
901  }
902  case 'X':
903  {
904  hex_capital = 1;
905  /* No break here is intended */
906  }
907  case 'x': /* Exec unsigned integer conversion to hexadecimal */
908  {
909  conv = 0;
910  /* Calculate greatest power of 16 for unsigned integer */
911  if(mod_l) { u_lim = API_POSIX_ULONG_MAX; }
912  else { u_lim = (long int) API_POSIX_UINT_MAX; }
913  u_p16 = 4096UL; exp = 3;
914  while(1)
915  {
916  if(16 > u_lim / u_p16) { break; }
917  u_p16 *= 16; ++exp;
918  }
919  /* Maximum required size: exponent + 1 + termination */
920  bn = exp + (size_t) 2;
921  /* Allocate buffer for conversion result */
922  b = (char*) api_posix_realloc((void*) b, bn);
923  if (NULL == b)
924  {
925  err_no = API_POSIX_ENOMEM; err = 1;
926  break;
927  }
928  /* Get unsigned value from stack */
929  if(mod_l) { u = va_arg(ap, unsigned long int); }
930  else { u = (unsigned long int) va_arg(ap, unsigned int); }
931  ci = 0;
932  /* Process digits */
933  ui = u_p16;
934  while(1UL <= ui)
935  {
936  if(u >= ui)
937  {
938  lz = 0;
939  u_dig = u / ui;
940  if(10UL > u_dig) { b[ci++] = (char) (48UL + u_dig); }
941  else
942  {
943  u_dig -= 10;
944  if(hex_capital) { b[ci++] = (char) (65UL + u_dig); }
945  else { b[ci++] = (char) (97UL + u_dig); }
946  }
947  u -= u / ui * ui;
948  }
949  else if(lz)
950  {
951  if(!u && 1UL == ui) { b[ci++] = '0'; }
952  else
953  {
954  /* Check minimum field width */
955  if(mod_w > exp)
956  {
957  if(!mod_z) { b[ci++] = ' '; }
958  else { b[ci++] = '0'; }
959  }
960  }
961  }
962  else { b[ci++] = '0'; }
963  ui /= 16UL; --exp;
964  if(ci >= bn) { break; }
965  }
966  /* Sanity check */
967  if(ci >= bn) { si = API_POSIX_SIZE_MAX; err = 1; break; }
968  /* Terminate conversion result */
969  b[ci] = 0;
970  c_s = b;
971  cn = ci;
972  break;
973  }
974  case 's': /* Exec string conversion */
975  {
976  if(mod_l)
977  {
978  PRINT_ERROR("snprintf(): Invalid modifier in conversion");
979  err = 1;
980  break;
981  }
982  conv = 0;
983  c_s = va_arg(ap, char*);
984  cn = strlen(c_s);
985  break;
986  }
987  default: /* Conversion type not supported */
988  {
989  PRINT_ERROR("snprintf(): Conversion type not supported");
990  /* Print a '?' to the output string */
991  b1[0] = '?';
992  b1[1] = 0;
993  c_s = b1;
994  cn = 1;
995  break;
996  }
997  }
998  if(err) break;
999  }
1000  if(err) break;
1001 
1002  /* Add up size of conversions */
1003  if(API_POSIX_SIZE_MAX - sn < cn) { sn = API_POSIX_SIZE_MAX; }
1004  else { sn += cn; }
1005 
1006  /* Copy conversion data to buffer */
1007  /* Attention: C99 allows (NULL == s) and (n == 0) */
1008  if(n && (si < n - 1) && cn)
1009  {
1010  if(n - 1 - si < cn) { cn = n - 1 - si; }
1011  memcpy(&s[si], c_s, cn);
1012  si += cn;
1013  s[si] = 0;
1014  }
1015  }
1016  api_posix_free((void*) b);
1017 
1018  /* Sanity check */
1019  if (si && si >= n)
1020  {
1021  /* This should really never happen! */
1022  PRINT_ERROR("snprintf(): AAARGHHH, buffer overflow detected!");
1023  /* Return error */
1024  err = 1;
1025  err_no = API_POSIX_EOVERFLOW;
1026  }
1027  }
1028 
1029  /* Set return value */
1030  if(!err) { res = (int) sn; }
1031  else
1032  {
1033  api_posix_errno = err_no;
1034  res = -1;
1035  }
1036 
1037  va_end(ap);
1038 
1039  return(res);
1040 }
1041 #endif /* CFG_USE_POSIX_API < 200112 */
1042 
1043 
1044 #if CFG_USE_POSIX_API < 200809
1045 /*
1046  * This implementation of 'getline()' should be POSIX.1-2008 conformant.
1047  * This function is thread safe if the underlaying 'fgetc()' is thread safe.
1048  */
1049 #define GETLINE_BLOCKSIZE (size_t) 128
1050 api_posix_ssize_t api_posix_getline(char** lineptr, size_t* n, FILE* stream)
1051 {
1052  api_posix_ssize_t res = -1;
1053  size_t len = *n;
1054  size_t pos = 0;
1055  char* memp;
1056  size_t mems = GETLINE_BLOCKSIZE;
1057  int rv = EOF;
1058  char termreq = 0;
1059 
1060  /* Check whether parameters are valid */
1061  if(NULL == lineptr || NULL == n) { api_posix_errno = API_POSIX_EINVAL; }
1062  else
1063  {
1064  /* Allocate line buffer if *lineptr is NULL */
1065  if (NULL == *lineptr)
1066  {
1067  *lineptr = (char*) api_posix_malloc(GETLINE_BLOCKSIZE);
1068  if(NULL == *lineptr) { api_posix_errno = API_POSIX_ENOMEM; }
1069  else { len = *n = GETLINE_BLOCKSIZE; }
1070  }
1071 
1072  /* Check for line buffer */
1073  if(NULL != *lineptr)
1074  {
1075  /* Limit usable buffer size if required */
1076  if((size_t) API_POSIX_SSIZE_MAX < len)
1077  {
1078  len = (size_t) API_POSIX_SSIZE_MAX;
1079  }
1080 
1081  /* Read data */
1082  while((size_t) API_POSIX_SSIZE_MAX > pos && mems)
1083  {
1084  if(pos >= len)
1085  {
1086  /* Allocate more memory */
1087  if(GETLINE_BLOCKSIZE > API_POSIX_SIZE_MAX - len)
1088  {
1089  mems = API_POSIX_SIZE_MAX - len;
1090  if(!mems)
1091  {
1092  api_posix_errno = API_POSIX_ENOMEM;
1093  break;
1094  }
1095  }
1096  memp = api_posix_realloc(*lineptr, *n + mems);
1097  if(NULL == memp)
1098  {
1099  api_posix_errno = API_POSIX_ENOMEM;
1100  break;
1101  }
1102  else
1103  {
1104  *lineptr = memp;
1105  *n += mems;
1106  len = *n;
1107  }
1108  }
1109 
1110  /* Check for termination request */
1111  if(termreq)
1112  {
1113  /* Terminate result string */
1114  (*lineptr)[pos] = 0;
1115  res = (api_posix_ssize_t) pos;
1116  break;
1117  }
1118 
1119  /* Get next character */
1120  rv = api_posix_fgetc(stream);
1121  if(EOF == rv) break;
1122  (*lineptr)[pos++] = (char) rv;
1123  if('\n' == (char) rv)
1124  {
1125  /* EOL detected */
1126  termreq = 1;
1127  }
1128  }
1129 
1130  /* Check for error */
1131  if((size_t) API_POSIX_SSIZE_MAX <= pos)
1132  {
1133  api_posix_errno = API_POSIX_EOVERFLOW;
1134  }
1135  }
1136  }
1137 
1138  return(res);
1139 }
1140 #endif /* CFG_USE_POSIX_API < 200809 */
1141 
1142 
1143 /* ========================================================================== */
1144 /* Address information */
1145 
1146 #if CFG_USE_POSIX_API < 200112 && !CFG_USE_IP6
1147 /* This implementation of 'freeaddrinfo()' should be POSIX.1-2008 conformant */
1148 void api_posix_freeaddrinfo(api_posix_struct_addrinfo* info)
1149 {
1150  api_posix_struct_addrinfo* next;
1151 
1152  while(NULL != info)
1153  {
1154  /* Store pointer to next object */
1155  next = info->ai_next;
1156  /* Free memory for current object */
1157  api_posix_free((void*) info);
1158  /* Continue with next object */
1159  info = next;
1160  }
1161 }
1162 
1163 
1164 /*
1165  * This implementation of 'getaddrinfo()' should be POSIX.1-2008 conformant with
1166  * the following exceptions:
1167  * - A nodename must be specified, resolving a service name alone is not
1168  * supported
1169  * - Only the flag AI_ADDRCONFIG is supported
1170  *
1171  * \attention
1172  * This emulation is based on deprecated functions that are removed from
1173  * POSIX.1-2008, this means it can only be used for older API versions.
1174  */
1175 static api_posix_pthread_mutex_t getaddrinfo_mutex
1176  = API_POSIX_PTHREAD_MUTEX_INITIALIZER;
1177 int api_posix_getaddrinfo(const char* nodename, const char* servname,
1178  const api_posix_struct_addrinfo* hints,
1179  api_posix_struct_addrinfo** result)
1180 {
1181  int res = 0;
1182  api_posix_struct_addrinfo* info = NULL;
1183  api_posix_struct_addrinfo* tmp = NULL;
1184  api_posix_struct_servent* se;
1185  api_posix_struct_hostent* he;
1186  size_t num = 0;
1187  api_posix_in_port_t port = 0;
1188  unsigned int p;
1189  int family = hints->ai_family;
1190 
1191  *result = NULL;
1192 
1193  /* Only IPv4 is supported */
1194  if(API_POSIX_AF_UNSPEC == family) { family = API_POSIX_AF_INET; }
1195 
1196  /* This implementation can't resolve service names alone */
1197  if(NULL == nodename)
1198  {
1199  PRINT_ERROR("getaddrinfo(): Call without nodename not supported");
1200  res = API_POSIX_EAI_FAIL;
1201  }
1202  else if(API_POSIX_AF_INET != family)
1203  {
1204  /* Address family not supported */
1205  res = API_POSIX_EAI_FAMILY;
1206  }
1207  else if (API_POSIX_SOCK_STREAM != hints->ai_socktype)
1208  {
1209  /* Socket type not supported */
1210  res = API_POSIX_EAI_SOCKTYPE;
1211  }
1212  else
1213  {
1214  if(NULL != servname)
1215  {
1216  /* Resolve service name using BSD API */
1217  if(api_posix_pthread_mutex_lock(&getaddrinfo_mutex))
1218  {
1219  PRINT_ERROR("getaddrinfo(): Locking mutex failed");
1220  res = API_POSIX_EAI_AGAIN;
1221  }
1222  else
1223  {
1224  /* Lock UI mutex */
1225  if(ts_lock_ui())
1226  {
1227  PRINT_ERROR("Locking UI mutex failed");
1228  res = API_POSIX_EAI_AGAIN;
1229  }
1230  else
1231  {
1232  /* Check whether a number is specified */
1233  if(1 == sscanf(servname, "%u", &p))
1234  {
1235  port = (api_posix_in_port_t)
1236  api_posix_htons((api_posix_in_port_t) p);
1237  }
1238  else
1239  {
1240  se = api_posix_getservbyname(servname, NULL);
1241  if(NULL != se) { port = (api_posix_in_port_t) se->s_port; }
1242  }
1243  /* Unlock UI mutex */
1244  if(ts_unlock_ui())
1245  {
1246  PRINT_ERROR("Unlocking UI mutex failed");
1247  }
1248  }
1249  /*
1250  * In a multithreading environment the systems hostent buffer may be
1251  * shared between all threads. Therefore the mutex must stay locked
1252  * until we have copied the data to our thread local buffer.
1253  */
1254  api_posix_pthread_mutex_unlock(&getaddrinfo_mutex);
1255  }
1256  }
1257 
1258  /* Resolve hostname to IPv4 address using BSD API */
1259  if(NULL != servname && !port)
1260  {
1261  PRINT_ERROR("getaddrinfo(): Service resolution failed");
1262  res = API_POSIX_EAI_FAIL;
1263  }
1264  else if(api_posix_pthread_mutex_lock(&getaddrinfo_mutex))
1265  {
1266  PRINT_ERROR("getaddrinfo(): Locking mutex failed");
1267  res = API_POSIX_EAI_AGAIN;
1268  }
1269  else
1270  {
1271  /* Lock UI mutex */
1272  if(ts_lock_ui())
1273  {
1274  PRINT_ERROR("Locking UI mutex failed");
1275  res = API_POSIX_EAI_AGAIN;
1276  }
1277  else
1278  {
1279  he = api_posix_gethostbyname(nodename);
1280  if(NULL == he) { res = API_POSIX_EAI_NONAME; }
1281  else if(API_POSIX_AF_INET != he->h_addrtype || 4 != he->h_length)
1282  {
1283  PRINT_ERROR("getaddrinfo(): Address type not supported");
1284  res = API_POSIX_EAI_FAIL;
1285  }
1286  else
1287  {
1288  do
1289  {
1290  /* Allocate memory for result object */
1291  tmp = (api_posix_struct_addrinfo*)
1292  api_posix_malloc(sizeof(api_posix_struct_addrinfo)
1293  + sizeof(api_posix_struct_sockaddr_in));
1294  if(NULL == tmp) { res = API_POSIX_EAI_MEMORY; break; }
1295  else
1296  {
1297  if(NULL == info) { *result = info = tmp; }
1298  else { info = info->ai_next = tmp; }
1299  /* Populate information structure */
1300  memset((void*) info, 0, sizeof(api_posix_struct_addrinfo));
1301  info->ai_family = API_POSIX_AF_INET;
1302  info->ai_socktype = hints->ai_socktype;
1303  info->ai_protocol = hints->ai_protocol;
1304  info->ai_addrlen = (api_posix_socklen_t)
1305  sizeof(api_posix_struct_sockaddr_in);
1306  info->ai_addr
1307  = (api_posix_struct_sockaddr*) (void*)
1308  ((char*) (void*) info
1309  + sizeof(api_posix_struct_addrinfo));
1310  ((api_posix_struct_sockaddr_in*) info->ai_addr)->sin_family
1311  = (api_posix_sa_family_t) info->ai_family;
1312  ((api_posix_struct_sockaddr_in*) info->ai_addr)->sin_port
1313  = port;
1314  ((api_posix_struct_sockaddr_in*) info->ai_addr)
1315  ->sin_addr.s_addr
1316  = *((api_posix_in_addr_t*) he->h_addr_list[num++]);
1317  info->ai_next = NULL;
1318 #if 0
1319  /* For debugging */
1320  printf("%s: %sgetaddrinfo(): Resolve result: "
1321  "%s / %s -> %s:%u\n", CFG_NAME, MAIN_ERR_PREFIX,
1322  nodename, servname,
1323  inet_ntoa(((api_posix_struct_sockaddr_in*)
1324  nfo->ai_addr)
1325  ->sin_addr),
1326  (unsigned int) api_posix_ntohs(port));
1327 
1328 #endif
1329  }
1330  }
1331  while(NULL != he->h_addr_list[num]);
1332  }
1333  /* Unlock UI mutex */
1334  if(ts_unlock_ui())
1335  {
1336  PRINT_ERROR("Unlocking UI mutex failed");
1337  }
1338  }
1339  /*
1340  * In a multithreading environment the systems hostent buffer may be
1341  * shared between all threads. Therefore the mutex must stay locked
1342  * until we have copied the data to our thread local buffer.
1343  */
1344  api_posix_pthread_mutex_unlock(&getaddrinfo_mutex);
1345 #if 0
1346  printf("%s: %sgetaddrinfo(): %u result(s)\n",
1347  CFG_NAME, MAIN_ERR_PREFIX, (unsigned int) num);
1348 #endif
1349  }
1350  }
1351 
1352  /* Free memory on error */
1353 #if 0
1354  printf("%s: %sgetaddrinfo(): Result: %d\n",
1355  CFG_NAME, MAIN_ERR_PREFIX, res);
1356 #endif
1357  if(res) { api_posix_freeaddrinfo(*result); }
1358 
1359  return(res);
1360 }
1361 
1362 
1363 /* This implementation of 'gai_strerror()' should be POSIX.1-2008 conformant */
1364 const char* api_posix_gai_strerror(int ecode)
1365 {
1366  const char* res;
1367 
1368  switch(ecode)
1369  {
1370  case API_POSIX_EAI_AGAIN:
1371  {
1372  res = "Temporary failure in name resolution";
1373  break;
1374  }
1375  case API_POSIX_EAI_BADFLAGS:
1376  {
1377  res = "Invalid value for parameter";
1378  break;
1379  }
1380  case API_POSIX_EAI_FAIL:
1381  {
1382  res = "Non-recoverable failure in name resolution";
1383  break;
1384  }
1385  case API_POSIX_EAI_FAMILY:
1386  {
1387  res = "Address family not supported";
1388  break;
1389  }
1390  case API_POSIX_EAI_MEMORY:
1391  {
1392  res = "Memory allocation failed";
1393  break;
1394  }
1395  case API_POSIX_EAI_NONAME:
1396  {
1397  res = "Name does not resolve for the supplied parameters";
1398  break;
1399  }
1400  case API_POSIX_EAI_OVERFLOW:
1401  {
1402  res = "Argument buffer overflowed (buffer too small)";
1403  break;
1404  }
1405  case API_POSIX_EAI_SERVICE:
1406  {
1407  res = "Service not recognized for the specified socket type";
1408  break;
1409  }
1410  case API_POSIX_EAI_SOCKTYPE:
1411  {
1412  res = "Socket type was not supported";
1413  break;
1414  }
1415  case API_POSIX_EAI_SYSTEM:
1416  {
1417  res = "System error occurred";
1418  break;
1419  }
1420  default:
1421  {
1422  res = "Unknown error occurred";
1423  break;
1424  }
1425  }
1426 
1427  return(res);
1428 }
1429 #endif /* CFG_USE_POSIX_API < 200112 && !CFG_USE_IP6 */
1430 
1431 
1432 /* ========================================================================== */
1433 /* BSD socket interface (XNS) */
1434 
1435 #if CFG_USE_POSIX_API < 200112
1436 int api_posix_inet_pton(int af, const char* src, void* dst)
1437 {
1438  int res = 1;
1439  api_posix_in_addr_t addr;
1440 
1441  if (API_POSIX_AF_INET != af)
1442  {
1443  api_posix_errno = API_POSIX_EAFNOSUPPORT;
1444  res = -1;
1445  }
1446 
1447  if(1 == res)
1448  {
1449  /* Typecast required if systems implementation is used */
1450  addr = (api_posix_in_addr_t) api_posix_inet_addr(src);
1451  if((api_posix_in_addr_t) -1 == addr) { res = 0; }
1452  else
1453  {
1454  /* The data in 'addr' is already in network byte order */
1455  *((unsigned char*) dst) = *((unsigned char*) &addr);
1456  *((unsigned char*) dst + 1) = *((unsigned char*) &addr + 1);
1457  *((unsigned char*) dst + 2) = *((unsigned char*) &addr + 2);
1458  *((unsigned char*) dst + 3) = *((unsigned char*) &addr + 3);
1459  }
1460  }
1461 
1462  return(res);
1463 }
1464 #endif /* CFG_USE_POSIX_API < 200112 */
1465 
1466 
1467 /* EOF */
MAIN_ERR_PREFIX
#define MAIN_ERR_PREFIX
Message prefix for POSIX module.
Definition: posix.c:106
PRINT_ERROR
#define PRINT_ERROR(s)
Prepend module prefix and print error message.
Definition: main.h:19
ts_unlock_ui
int ts_unlock_ui(void)
Unlock UI thread.
Definition: main.cxx:244
ts_lock_ui
int ts_lock_ui(void)
Lock UI thread.
Definition: main.cxx:217

Generated at 2026-01-27 using  doxygen