Coverage Report

Created: 2026-04-12 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/utils/wlog/Layout.c
Line
Count
Source
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * WinPR Logger
4
 *
5
 * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <winpr/config.h>
21
22
#include <stdio.h>
23
#include <string.h>
24
#include <stdarg.h>
25
26
#include <winpr/crt.h>
27
#include <winpr/assert.h>
28
#include <winpr/print.h>
29
#include <winpr/sysinfo.h>
30
#include <winpr/environment.h>
31
32
#include "wlog.h"
33
34
#include "Layout.h"
35
36
#if defined __linux__ && !defined ANDROID
37
#include <unistd.h>
38
#include <sys/syscall.h>
39
#endif
40
41
#ifndef MIN
42
830k
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
43
#endif
44
45
struct format_option_recurse;
46
47
struct format_tid_arg
48
{
49
  char tid[32];
50
};
51
52
struct format_option
53
{
54
  const char* fmt;
55
  size_t fmtlen;
56
  const char* replace;
57
  size_t replacelen;
58
  const char* (*fkt)(void*);
59
  union
60
  {
61
    void* pv;
62
    const void* cpv;
63
    size_t s;
64
  } arg;
65
  const char* (*ext)(const struct format_option* opt, const char* str, size_t* preplacelen,
66
                     size_t* pskiplen);
67
  struct format_option_recurse* recurse;
68
};
69
70
struct format_option_recurse
71
{
72
  struct format_option* options;
73
  size_t nroptions;
74
  wLog* log;
75
  wLogLayout* layout;
76
  const wLogMessage* message;
77
  char buffer[WLOG_MAX_PREFIX_SIZE];
78
};
79
80
/**
81
 * Log Layout
82
 */
83
WINPR_ATTR_FORMAT_ARG(3, 0)
84
static void WLog_PrintMessagePrefixVA(char* prefix, size_t prefixlen,
85
                                      WINPR_FORMAT_ARG const char* format, va_list args)
86
7.94M
{
87
7.94M
  (void)vsnprintf(prefix, prefixlen, format, args);
88
7.94M
}
89
90
WINPR_ATTR_FORMAT_ARG(3, 4)
91
static void WLog_PrintMessagePrefix(char* prefix, size_t prefixlen,
92
                                    WINPR_FORMAT_ARG const char* format, ...)
93
7.94M
{
94
7.94M
  va_list args = WINPR_C_ARRAY_INIT;
95
7.94M
  va_start(args, format);
96
7.94M
  WLog_PrintMessagePrefixVA(prefix, prefixlen, format, args);
97
7.94M
  va_end(args);
98
7.94M
}
99
100
static const char* get_tid(void* arg)
101
7.94M
{
102
7.94M
  struct format_tid_arg* targ = arg;
103
7.94M
  WINPR_ASSERT(targ);
104
105
7.94M
  size_t tid = 0;
106
7.94M
#if defined __linux__ && !defined ANDROID
107
  /* On Linux we prefer to see the LWP id */
108
7.94M
  tid = (size_t)syscall(SYS_gettid);
109
#else
110
  tid = (size_t)GetCurrentThreadId();
111
#endif
112
7.94M
  (void)_snprintf(targ->tid, sizeof(targ->tid), "%08" PRIxz, tid);
113
7.94M
  return targ->tid;
114
7.94M
}
115
116
static BOOL log_invalid_fmt(const char* what)
117
0
{
118
0
  (void)fprintf(stderr, "Invalid format string '%s'\n", what);
119
0
  return FALSE;
120
0
}
121
122
static BOOL check_and_log_format_size(char* format, size_t size, size_t index, size_t add)
123
250M
{
124
  /* format string must be '\0' terminated, so abort at size - 1 */
125
250M
  if (index + add + 1 >= size)
126
0
  {
127
0
    (void)fprintf(stderr,
128
0
                  "Format string too long ['%s', max %" PRIuz ", used %" PRIuz
129
0
                  ", adding %" PRIuz "]\n",
130
0
                  format, size, index, add);
131
0
    return FALSE;
132
0
  }
133
250M
  return TRUE;
134
250M
}
135
136
static int opt_compare_fn(const void* a, const void* b)
137
988M
{
138
988M
  const char* what = a;
139
988M
  const struct format_option* opt = b;
140
988M
  if (!opt)
141
0
    return -1;
142
988M
  return strncmp(what, opt->fmt, opt->fmtlen);
143
988M
}
144
145
static BOOL replace_format_string(const char* FormatString, struct format_option_recurse* recurse,
146
                                  char* format, size_t formatlen);
147
148
static const char* skip_if_null(const struct format_option* opt, const char* fmt,
149
                                size_t* preplacelen, size_t* pskiplen)
150
7.94M
{
151
7.94M
  WINPR_ASSERT(opt);
152
7.94M
  WINPR_ASSERT(fmt);
153
7.94M
  WINPR_ASSERT(preplacelen);
154
7.94M
  WINPR_ASSERT(pskiplen);
155
156
7.94M
  *preplacelen = 0;
157
7.94M
  *pskiplen = 0;
158
159
7.94M
  const char* str = &fmt[opt->fmtlen]; /* Skip first %{ from string */
160
7.94M
  const char* end = strstr(str, opt->replace);
161
7.94M
  if (!end)
162
0
    return nullptr;
163
7.94M
  *pskiplen = WINPR_ASSERTING_INT_CAST(size_t, end - fmt) + opt->replacelen;
164
165
7.94M
  if (!opt->arg.cpv)
166
7.11M
    return nullptr;
167
168
830k
  const size_t replacelen = WINPR_ASSERTING_INT_CAST(size_t, end - str);
169
170
830k
  char buffer[WLOG_MAX_PREFIX_SIZE] = WINPR_C_ARRAY_INIT;
171
830k
  memcpy(buffer, str, MIN(replacelen, ARRAYSIZE(buffer) - 1));
172
173
830k
  if (!replace_format_string(buffer, opt->recurse, opt->recurse->buffer,
174
830k
                             ARRAYSIZE(opt->recurse->buffer)))
175
0
    return nullptr;
176
177
830k
  *preplacelen = strnlen(opt->recurse->buffer, ARRAYSIZE(opt->recurse->buffer));
178
830k
  return opt->recurse->buffer;
179
830k
}
180
181
static BOOL replace_format_string(const char* FormatString, struct format_option_recurse* recurse,
182
                                  char* format, size_t formatlen)
183
8.77M
{
184
8.77M
  WINPR_ASSERT(FormatString);
185
8.77M
  WINPR_ASSERT(recurse);
186
187
8.77M
  size_t index = 0;
188
8.77M
  const char* prefix = WLog_GetGlobalPrefix();
189
8.77M
  if (prefix)
190
0
  {
191
0
    const int res = _snprintf(format, formatlen, "{%s}", prefix);
192
0
    if (res < 0)
193
0
      return FALSE;
194
0
    index = (size_t)res;
195
0
  }
196
197
257M
  while (*FormatString)
198
248M
  {
199
248M
    const struct format_option* opt =
200
248M
        bsearch(FormatString, recurse->options, recurse->nroptions,
201
248M
                sizeof(struct format_option), opt_compare_fn);
202
248M
    if (opt)
203
80.3M
    {
204
80.3M
      size_t replacelen = opt->replacelen;
205
80.3M
      size_t fmtlen = opt->fmtlen;
206
80.3M
      const char* replace = opt->replace;
207
80.3M
      const void* arg = opt->arg.cpv;
208
209
80.3M
      if (opt->ext)
210
7.94M
        replace = opt->ext(opt, FormatString, &replacelen, &fmtlen);
211
80.3M
      if (opt->fkt)
212
7.94M
        arg = opt->fkt(opt->arg.pv);
213
214
80.3M
      if (replace && (replacelen > 0))
215
73.1M
      {
216
73.1M
        WINPR_PRAGMA_DIAG_PUSH
217
73.1M
        WINPR_PRAGMA_DIAG_IGNORED_FORMAT_NONLITERAL
218
73.1M
        const int rc = _snprintf(&format[index], formatlen - index, replace, arg);
219
73.1M
        WINPR_PRAGMA_DIAG_POP
220
73.1M
        if (rc < 0)
221
0
          return FALSE;
222
73.1M
        if (!check_and_log_format_size(format, formatlen, index,
223
73.1M
                                       WINPR_ASSERTING_INT_CAST(size_t, rc)))
224
0
          return FALSE;
225
73.1M
        index += WINPR_ASSERTING_INT_CAST(size_t, rc);
226
73.1M
      }
227
80.3M
      FormatString += fmtlen;
228
80.3M
    }
229
168M
    else
230
168M
    {
231
      /* Unknown format string */
232
168M
      if (*FormatString == '%')
233
0
        return log_invalid_fmt(FormatString);
234
235
168M
      if (!check_and_log_format_size(format, formatlen, index, 1))
236
0
        return FALSE;
237
168M
      format[index++] = *FormatString++;
238
168M
    }
239
248M
  }
240
241
8.77M
  return check_and_log_format_size(format, formatlen, index, 0);
242
8.77M
}
243
244
BOOL WLog_Layout_GetMessagePrefix(wLog* log, wLogLayout* layout, const wLogMessage* message,
245
                                  char* prefix, size_t prefixlen)
246
7.94M
{
247
7.94M
  char format[WLOG_MAX_PREFIX_SIZE] = WINPR_C_ARRAY_INIT;
248
249
7.94M
  WINPR_ASSERT(layout);
250
7.94M
  WINPR_ASSERT(message);
251
7.94M
  WINPR_ASSERT(prefix);
252
253
7.94M
  struct format_tid_arg targ = WINPR_C_ARRAY_INIT;
254
255
7.94M
  SYSTEMTIME localTime = WINPR_C_ARRAY_INIT;
256
7.94M
  GetLocalTime(&localTime);
257
258
7.94M
  struct format_option_recurse recurse = {
259
7.94M
    .options = nullptr, .nroptions = 0, .log = log, .layout = layout, .message = message
260
7.94M
  };
261
262
270M
#define ENTRY(x) x, sizeof(x) - 1
263
7.94M
  struct format_option options[] = {
264
7.94M
    { ENTRY("%ctx"),
265
7.94M
      ENTRY("%s"),
266
7.94M
      log->custom,
267
7.94M
      { .pv = log->context },
268
7.94M
      nullptr,
269
7.94M
      &recurse }, /* log context */
270
7.94M
    { ENTRY("%dw"),
271
7.94M
      ENTRY("%u"),
272
7.94M
      nullptr,
273
7.94M
      { .s = localTime.wDayOfWeek },
274
7.94M
      nullptr,
275
7.94M
      &recurse }, /* day of week */
276
7.94M
    { ENTRY("%dy"),
277
7.94M
      ENTRY("%u"),
278
7.94M
      nullptr,
279
7.94M
      { .s = localTime.wDay },
280
7.94M
      nullptr,
281
7.94M
      &recurse }, /* day of year
282
                   */
283
7.94M
    { ENTRY("%fl"),
284
7.94M
      ENTRY("%s"),
285
7.94M
      nullptr,
286
7.94M
      { .cpv = message->FileName },
287
7.94M
      nullptr,
288
7.94M
      &recurse }, /* file
289
                   */
290
7.94M
    { ENTRY("%fn"),
291
7.94M
      ENTRY("%s"),
292
7.94M
      nullptr,
293
7.94M
      { .cpv = message->FunctionName },
294
7.94M
      nullptr,
295
7.94M
      &recurse }, /* function
296
                   */
297
7.94M
    { ENTRY("%hr"),
298
7.94M
      ENTRY("%02u"),
299
7.94M
      nullptr,
300
7.94M
      { .s = localTime.wHour },
301
7.94M
      nullptr,
302
7.94M
      &recurse }, /* hours
303
                   */
304
7.94M
    { ENTRY("%ln"),
305
7.94M
      ENTRY("%" PRIuz),
306
7.94M
      nullptr,
307
7.94M
      { .s = message->LineNumber },
308
7.94M
      nullptr,
309
7.94M
      &recurse }, /* line number */
310
7.94M
    { ENTRY("%lv"),
311
7.94M
      ENTRY("%s"),
312
7.94M
      nullptr,
313
7.94M
      { .cpv = WLOG_LEVELS[message->Level] },
314
7.94M
      nullptr,
315
7.94M
      &recurse }, /* log level */
316
7.94M
    { ENTRY("%mi"),
317
7.94M
      ENTRY("%02u"),
318
7.94M
      nullptr,
319
7.94M
      { .s = localTime.wMinute },
320
7.94M
      nullptr,
321
7.94M
      &recurse }, /* minutes
322
                   */
323
7.94M
    { ENTRY("%ml"),
324
7.94M
      ENTRY("%03u"),
325
7.94M
      nullptr,
326
7.94M
      { .s = localTime.wMilliseconds },
327
7.94M
      nullptr,
328
7.94M
      &recurse }, /* milliseconds */
329
7.94M
    { ENTRY("%mn"), ENTRY("%s"), nullptr, { .cpv = log->Name }, nullptr, &recurse }, /* module
330
                                                                                        name */
331
7.94M
    { ENTRY("%mo"),
332
7.94M
      ENTRY("%u"),
333
7.94M
      nullptr,
334
7.94M
      { .s = localTime.wMonth },
335
7.94M
      nullptr,
336
7.94M
      &recurse }, /* month
337
                   */
338
7.94M
    { ENTRY("%pid"),
339
7.94M
      ENTRY("%u"),
340
7.94M
      nullptr,
341
7.94M
      { .s = GetCurrentProcessId() },
342
7.94M
      nullptr,
343
7.94M
      &recurse }, /* process id */
344
7.94M
    { ENTRY("%se"),
345
7.94M
      ENTRY("%02u"),
346
7.94M
      nullptr,
347
7.94M
      { .s = localTime.wSecond },
348
7.94M
      nullptr,
349
7.94M
      &recurse },                                                                /* seconds
350
                                                                                  */
351
7.94M
    { ENTRY("%tid"), ENTRY("%s"), get_tid, { .pv = &targ }, nullptr, &recurse }, /* thread id */
352
7.94M
    { ENTRY("%yr"), ENTRY("%u"), nullptr, { .s = localTime.wYear }, nullptr, &recurse }, /* year
353
                                                                                          */
354
7.94M
    { ENTRY("%{"),
355
7.94M
      ENTRY("%}"),
356
7.94M
      nullptr,
357
7.94M
      { .pv = log->context },
358
7.94M
      skip_if_null,
359
7.94M
      &recurse }, /* skip if no context */
360
7.94M
  };
361
362
7.94M
  recurse.options = options;
363
7.94M
  recurse.nroptions = ARRAYSIZE(options);
364
365
7.94M
  if (!replace_format_string(layout->FormatString, &recurse, format, ARRAYSIZE(format)))
366
0
    return FALSE;
367
368
7.94M
  WINPR_PRAGMA_DIAG_PUSH
369
7.94M
  WINPR_PRAGMA_DIAG_IGNORED_FORMAT_SECURITY
370
371
7.94M
  WLog_PrintMessagePrefix(prefix, prefixlen, format);
372
373
7.94M
  WINPR_PRAGMA_DIAG_POP
374
375
7.94M
  return TRUE;
376
7.94M
}
377
378
wLogLayout* WLog_GetLogLayout(wLog* log)
379
0
{
380
0
  wLogAppender* appender = nullptr;
381
0
  appender = WLog_GetLogAppender(log);
382
0
  return appender->Layout;
383
0
}
384
385
BOOL WLog_Layout_SetPrefixFormat(WINPR_ATTR_UNUSED wLog* log, wLogLayout* layout,
386
                                 const char* format)
387
0
{
388
0
  free(layout->FormatString);
389
0
  layout->FormatString = nullptr;
390
391
0
  if (format)
392
0
  {
393
0
    layout->FormatString = _strdup(format);
394
395
0
    if (!layout->FormatString)
396
0
      return FALSE;
397
0
  }
398
399
0
  return TRUE;
400
0
}
401
402
wLogLayout* WLog_Layout_New(WINPR_ATTR_UNUSED wLog* log)
403
5
{
404
5
  LPCSTR prefix = "WLOG_PREFIX";
405
5
  DWORD nSize = 0;
406
5
  char* env = nullptr;
407
5
  wLogLayout* layout = nullptr;
408
5
  layout = (wLogLayout*)calloc(1, sizeof(wLogLayout));
409
410
5
  if (!layout)
411
0
    return nullptr;
412
413
5
  nSize = GetEnvironmentVariableA(prefix, nullptr, 0);
414
415
5
  if (nSize)
416
0
  {
417
0
    env = (LPSTR)malloc(nSize);
418
419
0
    if (!env)
420
0
    {
421
0
      free(layout);
422
0
      return nullptr;
423
0
    }
424
425
0
    if (GetEnvironmentVariableA(prefix, env, nSize) != nSize - 1)
426
0
    {
427
0
      free(env);
428
0
      free(layout);
429
0
      return nullptr;
430
0
    }
431
0
  }
432
433
5
  if (env)
434
0
    layout->FormatString = env;
435
5
  else
436
5
  {
437
#ifdef ANDROID
438
    layout->FormatString = _strdup("[pid=%pid:tid=%tid] - [%fn]%{[%ctx]%}: ");
439
#else
440
5
    layout->FormatString =
441
5
        _strdup("[%hr:%mi:%se:%ml] [%pid:%tid] [%lv][%mn] - [%fn]%{[%ctx]%}: ");
442
5
#endif
443
444
5
    if (!layout->FormatString)
445
0
    {
446
0
      free(layout);
447
0
      return nullptr;
448
0
    }
449
5
  }
450
451
5
  return layout;
452
5
}
453
454
void WLog_Layout_Free(WINPR_ATTR_UNUSED wLog* log, wLogLayout* layout)
455
5
{
456
5
  if (layout)
457
5
  {
458
5
    if (layout->FormatString)
459
5
    {
460
5
      free(layout->FormatString);
461
5
      layout->FormatString = nullptr;
462
5
    }
463
464
5
    free(layout);
465
5
  }
466
5
}