• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 19918707358

04 Dec 2025 05:12AM UTC coverage: 38.402% (-4.0%) from 42.395%
19918707358

push

github

saitoha
tests: fix meson msys dll lookup

9738 of 38220 branches covered (25.48%)

12841 of 33438 relevant lines covered (38.4%)

782420.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.27
/src/compat_stub.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to
8
 * deal in the Software without restriction, including without limitation the
9
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
 * sell copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24

25
#if !defined(_POSIX_C_SOURCE)
26
# define _POSIX_C_SOURCE 200809L
27
#endif
28

29
#if defined(_WIN32) && !defined(__STDC_WANT_SECURE_LIB__)
30
# define __STDC_WANT_SECURE_LIB__ 1
31
#endif
32

33
#if defined(_WIN32) && !defined(_CRT_DECLARE_NONSTDC_NAMES)
34
# define _CRT_DECLARE_NONSTDC_NAMES 1
35
#endif
36

37
#include "config.h"
38

39
/* STDC_HEADERS */
40
#include <errno.h>
41
#include <stdarg.h>
42
#include <stdio.h>
43
#include <stdlib.h>
44

45
#if HAVE_STRING_H
46
# include <string.h>
47
#endif
48
#if HAVE_STRINGS_H
49
# include <strings.h>
50
#endif
51
#if HAVE_FCNTL_H
52
# include <fcntl.h>
53
#endif
54
#if HAVE_SYS_TYPES_H
55
# include <sys/types.h>
56
#endif
57
#if HAVE_UNISTD_H
58
# include <unistd.h>
59
#endif
60

61
#if defined(_WIN32)
62
# include <io.h>
63
# include <direct.h>
64
#endif
65

66
#if defined(_MSC_VER)
67
# include <share.h>
68
#endif
69

70
#include "compat_stub.h"
71

72
#if defined(_WIN32) && (HAVE__DUPENV_S || defined(_MSC_VER))
73
/*
74
 * Some Windows SDKs require feature macros to expose `_dupenv_s()`.  The
75
 * declaration below acts as a safety net when headers remain silent even
76
 * after we request the secure CRT extensions.  Match the platform
77
 * attributes so that MinGW's dllimport decoration stays consistent.
78
 */
79
# if !defined(_CRTIMP)
80
#  define _CRTIMP
81
# endif
82
_CRTIMP errno_t __cdecl _dupenv_s(char **buffer,
83
                                  size_t *length,
84
                                  const char *name);
85
#endif
86

87
#if defined(__APPLE__) && defined(__clang__)
88
/*
89
 * +------------------------------------------------------------+
90
 * |  Fortified stdio vs. dynamic format strings                |
91
 * +------------------+-----------------------------------------+
92
 * | vsnprintf macro  |  __builtin___vsnprintf_chk              |
93
 * |------------------+-----------------------------------------|
94
 * | our wrapper call |           dynamic "format"              |
95
 * +------------------+-----------------------------------------+
96
 * Clang promotes vsnprintf() to a fortified builtin on Darwin.
97
 * The builtin insists on string literals so that it can audit
98
 * the format at compile time.  Our wrappers already perform the
99
 * format audit at the call site through the printf attribute, so
100
 * we explicitly opt out from the fortified macro and reach the
101
 * real function symbol instead.
102
 */
103
# undef vsnprintf
104
#endif
105

106
#if !defined(va_copy)
107
# define va_copy(dest, src) ((dest) = (src))
108
#endif
109

110
/*
111
 *  Formatting helpers
112
 *
113
 *  We provide a thin abstraction over the secure CRT whenever
114
 *  it is available.  On other platforms we keep using the
115
 *  classic interfaces.  All functions return the number of
116
 *  characters that would have been written, mirroring snprintf().
117
 */
118

119
SIXEL_COMPAT_API int
120
sixel_compat_vsnprintf(char *buffer,
1,265✔
121
                       size_t buffer_size,
122
                       const char *format,
123
                       va_list args)
124
{
125
    int written;
1,265✔
126
    va_list args_copy;
1,265✔
127
#if defined(_MSC_VER)
128
    int msvc_result;
129
#endif
130

131
    written = (-1);
1,265✔
132
    if (format == NULL) {
1,265!
133
        return written;
134
    }
135

136
#if defined(_MSC_VER)
137
    /*
138
     * +------------------------------------------------------------+
139
     * |  Dual-pass flow for the MSVC secure CRT                    |
140
     * +-------------------+----------------------------------------+
141
     * | 1. length probe   | _vscprintf() walks the argument list.  |
142
     * | 2. final write    | _vsnprintf_s() consumes the original   |
143
     * |                   | argument list while clamping writes.   |
144
     * +-------------------+----------------------------------------+
145
     * The secure CRT insists on receiving the untouched argument
146
     * list so we do not clone it for the second phase.  MinGW uses
147
     * a distinct runtime and follows the branch below instead.
148
     */
149
    va_copy(args_copy, args);
150
    written = _vscprintf(format, args_copy);
151
    va_end(args_copy);
152
    if (buffer_size > 0 && written >= 0) {
153
        msvc_result = _vsnprintf_s(buffer,
154
                                   buffer_size,
155
                                   _TRUNCATE,
156
                                   format,
157
                                   args);
158
        if (msvc_result < 0) {
159
            written = (-1);
160
        }
161
    }
162
#elif defined(_WIN32)
163
    va_copy(args_copy, args);
164
    written = _vscprintf(format, args_copy);
165
    va_end(args_copy);
166
    if (buffer_size > 0) {
167
        /*
168
         * +-------------------+-------------------------------+
169
         * | phase             | work performed                |
170
         * +-------------------+-------------------------------+
171
         * | 1. length probe   | _vscprintf() counts the bytes |
172
         * | 2. final write    | vsnprintf() copies to buffer  |
173
         * +-------------------+-------------------------------+
174
         * MinGW inherits the legacy MSVCRT behaviour where the
175
         * "NULL,0" probe fails.  The two-step dance above keeps
176
         * the interfaces happy on both Windows and POSIX.
177
         */
178
        va_copy(args_copy, args);
179
        (void)vsnprintf(buffer, buffer_size, format, args_copy);
180
        va_end(args_copy);
181
    }
182
#else
183
    va_copy(args_copy, args);
1,265✔
184
    written = vsnprintf(NULL, (size_t)0, format, args_copy);
1,265!
185
    va_end(args_copy);
1,265✔
186
    if (buffer_size > 0) {
1,265!
187
        va_copy(args_copy, args);
1,265✔
188
        (void)vsnprintf(buffer, buffer_size, format, args_copy);
1,265✔
189
        va_end(args_copy);
1,265✔
190
    }
437✔
191
#endif
192

193
    return written;
437✔
194
}
437✔
195

196

197

198
/*
199
 * Case-insensitive string comparison used by runtime configuration parsing.
200
 * The helper mirrors strcmp() semantics and tolerates NULL pairs so callers
201
 * can reuse it in defensive paths without extra guards.
202
 */
203
SIXEL_COMPAT_API int
204
sixel_compat_strcasecmp(char const *lhs, char const *rhs)
×
205
{
206
    int result;
×
207

208
    result = 0;
×
209

210
    if (lhs == NULL || rhs == NULL) {
×
211
        if (lhs == rhs) {
×
212
            return 0;
213
        }
214
        return (lhs == NULL) ? -1 : 1;
×
215
    }
216

217
#if defined(_WIN32)
218
    result = _stricmp(lhs, rhs);
219
#else
220
    result = strcasecmp(lhs, rhs);
×
221
#endif
222

223
    return result;
×
224
}
225

226

227
SIXEL_COMPAT_API int
228
sixel_compat_snprintf(char *buffer,
193✔
229
                      size_t buffer_size,
230
                      const char *format,
231
                      ...)
232
{
233
    int written;
193✔
234
    va_list args;
193✔
235

236
    written = (-1);
193✔
237
    va_start(args, format);
193✔
238
    written = sixel_compat_vsnprintf(buffer,
242✔
239
                                     buffer_size,
49✔
240
                                     format,
49✔
241
                                     args);
49✔
242
    va_end(args);
193✔
243

244
    return written;
242✔
245
}
49✔
246

247

248
/*
249
 *  String helpers
250
 *
251
 *  They copy textual data with explicit bounds so that we can
252
 *  rely on the secure CRT without replicating the pattern all
253
 *  across the project.
254
 */
255

256
SIXEL_COMPAT_API int
257
sixel_compat_strcpy(char *destination,
380✔
258
                    size_t destination_size,
259
                    const char *source)
260
{
261
    size_t length;
380✔
262

263
    if (destination == NULL || source == NULL || destination_size == 0) {
380!
264
        return (-1);
265
    }
266

267
#if defined(_MSC_VER)
268
    if (strcpy_s(destination, destination_size, source) != 0) {
269
        return (-1);
270
    }
271
    return (int)strlen(destination);
272
#else
273
    length = strlen(source);
380✔
274
    if (length >= destination_size) {
380!
275
        length = destination_size - 1;
×
276
    }
277
    memcpy(destination, source, length);
380✔
278
    destination[length] = '\0';
380✔
279
    return (int)length;
380✔
280
#endif
281
}
110✔
282

283

284
SIXEL_COMPAT_API char *
285
sixel_compat_strerror(int error_number,
×
286
                      char *buffer,
287
                      size_t buffer_size)
288
{
289
#if defined(_MSC_VER)
290
    errno_t status;
291
#elif defined(_WIN32)
292
# if defined(__STDC_LIB_EXT1__)
293
    errno_t status;
294
# else
295
    char *message;
296
    size_t copy_length;
297
# endif
298
#else
299
# if defined(_GNU_SOURCE)
300
    char *message;
301
    size_t copy_length;
302
# endif
303
#endif
304

305
    if (buffer == NULL || buffer_size == 0) {
×
306
        return NULL;
307
    }
308

309
#if defined(_MSC_VER)
310
    status = strerror_s(buffer, buffer_size, error_number);
311
    if (status != 0) {
312
        buffer[0] = '\0';
313
        return NULL;
314
    }
315
    return buffer;
316
#elif defined(_WIN32)
317
    /*
318
     * +----------------------------------------------------+
319
     * |  Windows family error messages                     |
320
     * +----------------------------------------------------+
321
     * |  CRT flavor  |  Routine we can rely on             |
322
     * |------------- +-------------------------------------|
323
     * |  Annex K     |  strerror_s()                       |
324
     * |  Legacy      |  strerror() + manual copy           |
325
     * +----------------------------------------------------+
326
     * The secure CRT is present both with MSVC and with
327
     * clang + UCRT.  When Annex K is unavailable we fall
328
     * back to strerror() while still clamping the output.
329
     */
330
# if defined(__STDC_LIB_EXT1__)
331
    status = strerror_s(buffer, buffer_size, error_number);
332
    if (status != 0) {
333
        buffer[0] = '\0';
334
        return NULL;
335
    }
336
    return buffer;
337
# else
338
    message = strerror(error_number);
339
    if (message == NULL) {
340
        buffer[0] = '\0';
341
        return NULL;
342
    }
343
    copy_length = buffer_size - 1;
344
    (void)strncpy(buffer, message, copy_length);
345
    buffer[buffer_size - 1] = '\0';
346
    return buffer;
347
# endif
348
#else
349
# if defined(_GNU_SOURCE)
350
    message = strerror_r(error_number, buffer, buffer_size);
351
    if (message == NULL) {
352
        return NULL;
353
    }
354
    if (message != buffer) {
355
        copy_length = buffer_size - 1;
356
        (void)strncpy(buffer, message, copy_length);
357
        buffer[buffer_size - 1] = '\0';
358
    }
359
    return buffer;
360
# else
361
    if (strerror_r(error_number, buffer, buffer_size) != 0) {
×
362
        buffer[0] = '\0';
×
363
        return NULL;
×
364
    }
365
    return buffer;
366
# endif
367
#endif
368
}
369

370

371
SIXEL_COMPAT_API FILE *
372
sixel_compat_fopen(const char *filename, const char *mode)
55✔
373
{
374
    FILE *handle;
55✔
375

376
    handle = NULL;
55✔
377
    if (filename == NULL || mode == NULL) {
55!
378
        errno = EINVAL;
×
379
        return NULL;
×
380
    }
381

382
#if defined(_MSC_VER)
383
    /*
384
     * Windows refuses to reopen a file for reading when another descriptor
385
     * still holds an exclusive write handle.  `_fsopen()` lets us request
386
     * the POSIX-style behaviour where read/write callers can share the same
387
     * file.  The diagram below sketches the handshake between the writer and
388
     * reader:
389
     *
390
     *   writer (encoder)  ---- creates temp file ---->  `png_temp_path`
391
     *           |                                         |
392
     *           |<--- `_fsopen(..., _SH_DENYNO)` --- reader (decoder)
393
     *
394
     * Both sides now share the handle without tripping over `_SH_DENYWR`
395
     * defaults, mirroring how Unix would allow the concurrent access.
396
     */
397
    handle = _fsopen(filename, mode, _SH_DENYNO);
398
#else
399
    handle = fopen(filename, mode);
55✔
400
#endif
401

402
    return handle;
55✔
403
}
16✔
404

405

406
SIXEL_COMPAT_API const char *
407
sixel_compat_getenv(const char *name)
4,274✔
408
{
409
#if HAVE__DUPENV_S || defined(_MSC_VER)
410
    static char buffer[32768];
411
    char *value;
412
    size_t length;
413
    errno_t status;
414

415
    value = NULL;
416
    length = 0u;
417
    status = 0;
418

419
    if (name == NULL) {
420
        return NULL;
421
    }
422

423
    /*
424
     * `_dupenv_s()` allocates the buffer for us and reports the byte count
425
     * in `length`.  Guard against values longer than our static buffer to
426
     * avoid truncation.
427
     */
428
    status = _dupenv_s(&value, &length, name);
429
    if (status != 0) {
430
        if (value != NULL) {
431
            free(value);
432
        }
433
        return NULL;
434
    }
435
    if (value == NULL) {
436
        return NULL;
437
    }
438
    if (length >= sizeof(buffer)) {
439
        free(value);
440
        return NULL;
441
    }
442

443
    memcpy(buffer, value, length);
444
    buffer[length] = '\0';
445
    free(value);
446

447
    return buffer;
448
#else
449
    return getenv(name);
4,274✔
450
#endif
451
}
452

453

454
SIXEL_COMPAT_API int
455
sixel_compat_setenv(const char *name, const char *value)
1,742✔
456
{
457
#if defined(_WIN32)
458
    errno_t status;
459

460
    status = 0;
461
    if (name == NULL || value == NULL) {
462
        errno = EINVAL;
463
        return (-1);
464
    }
465

466
    status = _putenv_s(name, value);
467
    if (status != 0) {
468
        errno = status;
469
        return (-1);
470
    }
471

472
    return 0;
473
#else
474
    if (name == NULL || value == NULL) {
1,742!
475
        errno = EINVAL;
×
476
        return (-1);
×
477
    }
478

479
    if (setenv(name, value, 1) != 0) {
1,742!
480
        return (-1);
481
    }
482

483
    return 0;
542✔
484
#endif
485
}
542✔
486

487

488
SIXEL_COMPAT_API char *
489
sixel_compat_strtok(char *string,
16✔
490
                    const char *delimiters,
491
                    char **context)
492
{
493
#if defined(_MSC_VER)
494
    return strtok_s(string, delimiters, context);
495
#elif defined(_POSIX_VERSION)
496
    return strtok_r(string, delimiters, context);
16✔
497
#else
498
    (void)context;
499
    return strtok(string, delimiters);
500
#endif
501
}
502

503

504
SIXEL_COMPAT_API int
505
sixel_compat_mktemp(char *templ, size_t buffer_size)
18✔
506
{
507
#if defined(_MSC_VER)
508
    errno_t error;
509

510
    if (templ == NULL || buffer_size == 0) {
511
        errno = EINVAL;
512
        return (-1);
513
    }
514
    error = _mktemp_s(templ, buffer_size);
515
    if (error != 0) {
516
        errno = error;
517
        return (-1);
518
    }
519
    return 0;
520
#elif HAVE_MKSTEMP
521
    int fd;
18✔
522

523
    (void)buffer_size;
18✔
524

525
    if (templ == NULL) {
18!
526
        errno = EINVAL;
×
527
        return (-1);
×
528
    }
529
    fd = mkstemp(templ);
18✔
530
    if (fd >= 0) {
18!
531
        (void)sixel_compat_close(fd);
22✔
532
        (void)sixel_compat_unlink(templ);
18✔
533
        return 0;
18✔
534
    }
535
    return (-1);
536
#elif HAVE_MKTEMP
537
    (void)buffer_size;
538

539
    if (templ == NULL) {
540
        errno = EINVAL;
541
        return (-1);
542
    }
543
    if (mktemp(templ) == NULL) {
544
        return (-1);
545
    }
546
    return 0;
547
#else
548
    (void)templ;
549
    (void)buffer_size;
550
    errno = ENOSYS;
551
    return (-1);
552
#endif
553
}
6✔
554

555

556
SIXEL_COMPAT_API int
557
sixel_compat_open(const char *path, int flags, ...)
58✔
558
{
559
    int fd;
58✔
560
    va_list args;
58✔
561
    int mode;
58✔
562

563
    fd = (-1);
58✔
564
    mode = 0;
58✔
565

566
    if (path == NULL) {
58!
567
        errno = EINVAL;
×
568
        return (-1);
×
569
    }
570

571
    va_start(args, flags);
58✔
572
    if (flags & O_CREAT) {
58!
573
        mode = va_arg(args, int);
58✔
574
    }
16✔
575
    va_end(args);
58✔
576

577
#if defined(_MSC_VER)
578
    fd = _open(path, flags, mode);
579
#else
580
    if (flags & O_CREAT) {
58!
581
        fd = open(path, flags, (mode_t)mode);
58!
582
    } else {
16✔
583
        fd = open(path, flags);
×
584
    }
585
#endif
586

587
    return fd;
16✔
588
}
16✔
589

590

591
SIXEL_COMPAT_API int
592
sixel_compat_close(int fd)
68✔
593
{
594
#if defined(_MSC_VER)
595
    return _close(fd);
596
#else
597
    return close(fd);
68✔
598
#endif
599
}
600

601

602
SIXEL_COMPAT_API int
603
sixel_compat_unlink(const char *path)
36✔
604
{
605
#if defined(_MSC_VER)
606
    return _unlink(path);
607
#else
608
    return unlink(path);
36✔
609
#endif
610
}
611

612

613
SIXEL_COMPAT_API int
614
sixel_compat_access(const char *path, int mode)
×
615
{
616
#if defined(_MSC_VER)
617
    return _access(path, mode);
618
#else
619
    return access(path, mode);
×
620
#endif
621
}
622

623

624
SIXEL_COMPAT_API ssize_t
625
sixel_compat_write(int fd, const void *buffer, size_t count)
7,650✔
626
{
627
#if defined(_MSC_VER)
628
    return (ssize_t)_write(fd, buffer, (unsigned int)count);
629
#else
630
    return write(fd, buffer, count);
7,650✔
631
#endif
632
}
633

634

635
/* emacs Local Variables:      */
636
/* emacs mode: c               */
637
/* emacs tab-width: 4          */
638
/* emacs indent-tabs-mode: nil */
639
/* emacs c-basic-offset: 4     */
640
/* emacs End:                  */
641
/* vim: set expandtab ts=4 sts=4 sw=4 : */
642
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc