Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_vsi_error.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  VSI Virtual File System
4
 * Purpose:  Implement an error system for reporting file system errors.
5
 *           Filesystem errors need to be handled separately from the
6
 *           CPLError architecture because they are potentially ignored.
7
 * Author:   Rob Emanuele, rdemanuele at gmail.com
8
 *
9
 ******************************************************************************
10
 * Copyright (c) 2016, Rob Emanuele <rdemanuele at gmail.com>
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "cpl_vsi_error.h"
16
17
#include <cstdarg>
18
#include <cstdio>
19
20
#include "cpl_config.h"
21
#include "cpl_conv.h"
22
#include "cpl_error.h"
23
#include "cpl_multiproc.h"
24
#include "cpl_string.h"
25
#include "cpl_vsi.h"
26
27
#if !defined(va_copy) && defined(__va_copy)
28
#define va_copy __va_copy
29
#endif
30
31
// TODO(rouault): Why is this here?
32
#if !defined(_WIN32)
33
#include <string.h>
34
#endif
35
36
#define TIMESTAMP_DEBUG
37
// #define MEMORY_DEBUG
38
39
constexpr int DEFAULT_LAST_ERR_MSG_SIZE =
40
#if !defined(HAVE_VSNPRINTF)
41
    20000
42
#else
43
    500
44
#endif
45
    ;
46
47
typedef struct
48
{
49
    VSIErrorNum nLastErrNo;
50
    int nLastErrMsgMax;
51
    char szLastErrMsg[DEFAULT_LAST_ERR_MSG_SIZE];
52
    // Do not add anything here. szLastErrMsg must be the last field. See
53
    // CPLRealloc() below.
54
} VSIErrorContext;
55
56
/************************************************************************/
57
/*                         CPLGetErrorContext()                         */
58
/************************************************************************/
59
60
static VSIErrorContext *VSIGetErrorContext()
61
62
4.84M
{
63
4.84M
    int bError = FALSE;
64
4.84M
    VSIErrorContext *psCtx = reinterpret_cast<VSIErrorContext *>(
65
4.84M
        CPLGetTLSEx(CTLS_VSIERRORCONTEXT, &bError));
66
4.84M
    if (bError)
67
0
        return nullptr;
68
69
4.84M
    if (psCtx == nullptr)
70
42
    {
71
42
        psCtx = static_cast<VSIErrorContext *>(
72
42
            VSICalloc(sizeof(VSIErrorContext), 1));
73
42
        if (psCtx == nullptr)
74
0
        {
75
0
            fprintf(stderr, /*ok*/
76
0
                    "Out of memory attempting to record a VSI error.\n");
77
0
            return nullptr;
78
0
        }
79
42
        psCtx->nLastErrNo = VSIE_None;
80
42
        psCtx->nLastErrMsgMax = sizeof(psCtx->szLastErrMsg);
81
42
        CPLSetTLS(CTLS_VSIERRORCONTEXT, psCtx, TRUE);
82
42
    }
83
84
4.84M
    return psCtx;
85
4.84M
}
86
87
/************************************************************************/
88
/*                             VSIErrorV()                              */
89
/************************************************************************/
90
91
static void VSIErrorV(VSIErrorNum err_no, const char *fmt, va_list args)
92
519k
{
93
519k
    VSIErrorContext *psCtx = VSIGetErrorContext();
94
519k
    if (psCtx == nullptr)
95
0
        return;
96
97
/* -------------------------------------------------------------------- */
98
/*      Expand the error message                                        */
99
/* -------------------------------------------------------------------- */
100
519k
#if defined(HAVE_VSNPRINTF)
101
519k
    {
102
519k
        va_list wrk_args;
103
104
519k
#ifdef va_copy
105
519k
        va_copy(wrk_args, args);
106
#else
107
        wrk_args = args;
108
#endif
109
110
519k
        int nPreviousSize = 0;
111
519k
        int nPR = 0;
112
519k
        while (((nPR = CPLvsnprintf(psCtx->szLastErrMsg + nPreviousSize,
113
519k
                                    psCtx->nLastErrMsgMax - nPreviousSize, fmt,
114
519k
                                    wrk_args)) == -1 ||
115
519k
                nPR >= psCtx->nLastErrMsgMax - nPreviousSize - 1) &&
116
23
               psCtx->nLastErrMsgMax < 1000000)
117
23
        {
118
23
#ifdef va_copy
119
23
            va_end(wrk_args);
120
23
            va_copy(wrk_args, args);
121
#else
122
            wrk_args = args;
123
#endif
124
23
            psCtx->nLastErrMsgMax *= 3;
125
23
            psCtx = static_cast<VSIErrorContext *>(CPLRealloc(
126
23
                psCtx, sizeof(VSIErrorContext) - DEFAULT_LAST_ERR_MSG_SIZE +
127
23
                           psCtx->nLastErrMsgMax + 1));
128
23
            CPLSetTLS(CTLS_VSIERRORCONTEXT, psCtx, TRUE);
129
23
        }
130
131
519k
        va_end(wrk_args);
132
519k
    }
133
#else  // !HAVE_VSNPRINTF
134
    CPLvsnprintf(psCtx->szLastErrMsg, psCtx->nLastErrMsgMax, fmt, args);
135
#endif
136
137
519k
    psCtx->nLastErrNo = err_no;
138
519k
}
139
140
/**********************************************************************
141
 *                          VSIError()
142
 **********************************************************************/
143
144
/**
145
 * Report a VSI filesystem error.
146
 *
147
 * This function records an error in the filesystem that may or may not be
148
 * used in the future, for example converted into a CPLError. This allows
149
 * filesystem errors to be available to error handling functionality, but
150
 * reported only when necessary.
151
 *
152
 * @param err_no the error number (VSIE_*) from cpl_vsi_error.h.
153
 * @param fmt a printf() style format string.  Any additional arguments
154
 * will be treated as arguments to fill in this format in a manner
155
 * similar to printf().
156
 */
157
158
void VSIError(VSIErrorNum err_no, CPL_FORMAT_STRING(const char *fmt), ...)
159
519k
{
160
519k
    va_list args;
161
162
    // Expand the error message.
163
519k
    va_start(args, fmt);
164
519k
    VSIErrorV(err_no, fmt, args);
165
519k
    va_end(args);
166
519k
}
167
168
/**********************************************************************
169
 *                          VSIErrorReset()
170
 **********************************************************************/
171
172
/**
173
 * Erase any traces of previous errors.
174
 *
175
 * This is used to clear out the latest file system error when it is either
176
 * translated into a CPLError call or when it is determined to be ignorable.
177
 */
178
179
void CPL_STDCALL VSIErrorReset()
180
3.46M
{
181
3.46M
    VSIErrorContext *psCtx = VSIGetErrorContext();
182
3.46M
    if (psCtx == nullptr)
183
0
        return;
184
185
3.46M
    psCtx->nLastErrNo = VSIE_None;
186
3.46M
    psCtx->szLastErrMsg[0] = '\0';
187
3.46M
}
188
189
/**********************************************************************
190
 *                          VSIGetLastErrorNo()
191
 **********************************************************************/
192
193
/**
194
 * Fetch the last error number.
195
 *
196
 * Fetches the last error number posted with VSIError(), that hasn't
197
 * been cleared by VSIErrorReset().  This is the error number, not the error
198
 * class.
199
 *
200
 * @return the error number of the last error to occur, or VSIE_None (0)
201
 * if there are no posted errors.
202
 */
203
204
VSIErrorNum CPL_STDCALL VSIGetLastErrorNo()
205
474k
{
206
474k
    VSIErrorContext *psCtx = VSIGetErrorContext();
207
474k
    if (psCtx == nullptr)
208
0
        return 0;
209
210
474k
    return psCtx->nLastErrNo;
211
474k
}
212
213
/**********************************************************************
214
 *                          VSIGetLastErrorMsg()
215
 **********************************************************************/
216
217
/**
218
 * Get the last error message.
219
 *
220
 * Fetches the last error message posted with VSIError(), that hasn't
221
 * been cleared by VSIErrorReset().  The returned pointer is to an internal
222
 * string that should not be altered or freed.
223
 *
224
 * @return the last error message, or NULL if there is no posted error
225
 * message.
226
 */
227
228
const char *CPL_STDCALL VSIGetLastErrorMsg()
229
389k
{
230
389k
    VSIErrorContext *psCtx = VSIGetErrorContext();
231
389k
    if (psCtx == nullptr)
232
0
        return "";
233
234
389k
    return psCtx->szLastErrMsg;
235
389k
}
236
237
/**********************************************************************
238
 *                     VSIErrorNumToString()
239
 **********************************************************************/
240
241
/** Translate a VSI error number into a string.
242
 *
243
 * @since GDAL 3.12
244
 */
245
const char *VSIErrorNumToString(int eErr)
246
1.18k
{
247
1.18k
#define CASE(x)                                                                \
248
1.18k
    case VSIE_##x:                                                             \
249
1.18k
        return #x;
250
1.18k
    switch (eErr)
251
1.18k
    {
252
0
        CASE(None)
253
0
        CASE(FileError)
254
0
        CASE(HttpError)
255
0
        CASE(ObjectStorageGenericError)
256
0
        CASE(AccessDenied)
257
0
        CASE(BucketNotFound)
258
0
        CASE(ObjectNotFound)
259
1.18k
        CASE(InvalidCredentials)
260
0
        CASE(SignatureDoesNotMatch)
261
0
        default:
262
0
            break;
263
1.18k
    }
264
0
#undef CASE
265
0
    CPLAssert(false);
266
0
    return "UnknownError";
267
1.18k
}
268
269
/**********************************************************************
270
 *                          VSItoCPLError()
271
 **********************************************************************/
272
273
/**
274
 * Translate the VSI error into a CPLError call
275
 *
276
 * If there is a VSIError that is set, translate it to a CPLError call
277
 * with the given CPLErr error class, and either an appropriate CPLErrorNum
278
 * given the VSIErrorNum, or the given default CPLErrorNum.
279
 *
280
 * @return TRUE if a CPLError was issued, or FALSE if not.
281
 */
282
283
int CPL_STDCALL VSIToCPLError(CPLErr eErrClass, CPLErrorNum eDefaultErrorNo)
284
462k
{
285
462k
    const int err = VSIGetLastErrorNo();
286
462k
    switch (err)
287
462k
    {
288
73.4k
        case VSIE_None:
289
73.4k
            return FALSE;
290
384k
        case VSIE_FileError:
291
384k
            CPLError(eErrClass, eDefaultErrorNo, "%s", VSIGetLastErrorMsg());
292
384k
            break;
293
2.91k
        case VSIE_HttpError:
294
2.91k
            CPLError(eErrClass, CPLE_HttpResponse, "%s", VSIGetLastErrorMsg());
295
2.91k
            break;
296
0
        case VSIE_ObjectStorageGenericError:
297
0
            CPLError(eErrClass, CPLE_ObjectStorageGenericError, "%s",
298
0
                     VSIGetLastErrorMsg());
299
0
            break;
300
0
        case VSIE_AccessDenied:
301
0
            CPLError(eErrClass, CPLE_AccessDenied, "%s: %s",
302
0
                     VSIErrorNumToString(err), VSIGetLastErrorMsg());
303
0
            break;
304
0
        case VSIE_BucketNotFound:
305
0
            CPLError(eErrClass, CPLE_BucketNotFound, "%s: %s",
306
0
                     VSIErrorNumToString(err), VSIGetLastErrorMsg());
307
0
            break;
308
0
        case VSIE_ObjectNotFound:
309
0
            CPLError(eErrClass, CPLE_ObjectNotFound, "%s: %s",
310
0
                     VSIErrorNumToString(err), VSIGetLastErrorMsg());
311
0
            break;
312
1.18k
        case VSIE_InvalidCredentials:
313
1.18k
            CPLError(eErrClass, CPLE_InvalidCredentials, "%s: %s",
314
1.18k
                     VSIErrorNumToString(err), VSIGetLastErrorMsg());
315
1.18k
            break;
316
0
        case VSIE_SignatureDoesNotMatch:
317
0
            CPLError(eErrClass, CPLE_SignatureDoesNotMatch, "%s: %s",
318
0
                     VSIErrorNumToString(err), VSIGetLastErrorMsg());
319
0
            break;
320
0
        default:
321
0
            CPLError(eErrClass, CPLE_HttpResponse,
322
0
                     "A filesystem error with code %d occurred", err);
323
0
            break;
324
462k
    }
325
326
388k
    return TRUE;
327
462k
}
328
329
/**********************************************************************
330
 *                        VSIToCPLErrorWithMsg()
331
 **********************************************************************/
332
333
/**
334
 * Translate the VSI error into a CPLError call
335
 *
336
 * If there is a VSIError that is set, translate it to a CPLError call
337
 * with the given CPLErr error class, and either an appropriate CPLErrorNum
338
 * given the VSIErrorNum, or the given default CPLErrorNum.
339
 */
340
341
void VSIToCPLErrorWithMsg(CPLErr eErrClass, CPLErrorNum eDefaultErrorNo,
342
                          const char *pszMsg)
343
18
{
344
18
    const int err = VSIGetLastErrorNo();
345
18
    switch (err)
346
18
    {
347
6
        case VSIE_None:
348
6
            CPLError(eErrClass, eDefaultErrorNo, "%s", pszMsg);
349
6
            break;
350
12
        case VSIE_FileError:
351
12
            CPLError(eErrClass, eDefaultErrorNo, "%s: %s", pszMsg,
352
12
                     VSIGetLastErrorMsg());
353
12
            break;
354
0
        case VSIE_HttpError:
355
0
            CPLError(eErrClass, CPLE_HttpResponse, "%s: %s", pszMsg,
356
0
                     VSIGetLastErrorMsg());
357
0
            break;
358
0
        case VSIE_ObjectStorageGenericError:
359
0
            CPLError(eErrClass, CPLE_ObjectStorageGenericError, "%s: %s",
360
0
                     pszMsg, VSIGetLastErrorMsg());
361
0
            break;
362
0
        case VSIE_AccessDenied:
363
0
            CPLError(eErrClass, CPLE_AccessDenied, "%s: %s", pszMsg,
364
0
                     VSIGetLastErrorMsg());
365
0
            break;
366
0
        case VSIE_BucketNotFound:
367
0
            CPLError(eErrClass, CPLE_BucketNotFound, "%s: %s", pszMsg,
368
0
                     VSIGetLastErrorMsg());
369
0
            break;
370
0
        case VSIE_ObjectNotFound:
371
0
            CPLError(eErrClass, CPLE_ObjectNotFound, "%s: %s", pszMsg,
372
0
                     VSIGetLastErrorMsg());
373
0
            break;
374
0
        case VSIE_InvalidCredentials:
375
0
            CPLError(eErrClass, CPLE_InvalidCredentials, "%s: %s", pszMsg,
376
0
                     VSIGetLastErrorMsg());
377
0
            break;
378
0
        case VSIE_SignatureDoesNotMatch:
379
0
            CPLError(eErrClass, CPLE_SignatureDoesNotMatch, "%s: %s", pszMsg,
380
0
                     VSIGetLastErrorMsg());
381
0
            break;
382
0
        default:
383
0
            CPLError(eErrClass, CPLE_HttpResponse,
384
0
                     "%s: A filesystem error with code %d occurred", pszMsg,
385
0
                     err);
386
0
            break;
387
18
    }
388
18
}