Coverage Report

Created: 2025-07-01 06:46

/src/FreeRDP/winpr/libwinpr/utils/wlog/Layout.c
Line
Count
Source (jump to first uncovered line)
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
0
#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
  void* arg;
60
  const char* (*ext)(const struct format_option* opt, const char* str, size_t* preplacelen,
61
                     size_t* pskiplen);
62
  struct format_option_recurse* recurse;
63
};
64
65
struct format_option_recurse
66
{
67
  struct format_option* options;
68
  size_t nroptions;
69
  wLog* log;
70
  wLogLayout* layout;
71
  wLogMessage* message;
72
  char buffer[WLOG_MAX_PREFIX_SIZE];
73
};
74
75
/**
76
 * Log Layout
77
 */
78
WINPR_ATTR_FORMAT_ARG(3, 0)
79
static void WLog_PrintMessagePrefixVA(WINPR_ATTR_UNUSED wLog* log, wLogMessage* message,
80
                                      WINPR_FORMAT_ARG const char* format, va_list args)
81
1.01k
{
82
1.01k
  WINPR_ASSERT(message);
83
1.01k
  (void)vsnprintf(message->PrefixString, WLOG_MAX_PREFIX_SIZE - 1, format, args);
84
1.01k
}
85
86
WINPR_ATTR_FORMAT_ARG(3, 4)
87
static void WLog_PrintMessagePrefix(wLog* log, wLogMessage* message,
88
                                    WINPR_FORMAT_ARG const char* format, ...)
89
1.01k
{
90
1.01k
  va_list args;
91
1.01k
  va_start(args, format);
92
1.01k
  WLog_PrintMessagePrefixVA(log, message, format, args);
93
1.01k
  va_end(args);
94
1.01k
}
95
96
static const char* get_tid(void* arg)
97
1.01k
{
98
1.01k
  struct format_tid_arg* targ = arg;
99
1.01k
  WINPR_ASSERT(targ);
100
101
1.01k
  size_t tid = 0;
102
1.01k
#if defined __linux__ && !defined ANDROID
103
  /* On Linux we prefer to see the LWP id */
104
1.01k
  tid = (size_t)syscall(SYS_gettid);
105
#else
106
  tid = (size_t)GetCurrentThreadId();
107
#endif
108
1.01k
  (void)_snprintf(targ->tid, sizeof(targ->tid), "%08" PRIxz, tid);
109
1.01k
  return targ->tid;
110
1.01k
}
111
112
static BOOL log_invalid_fmt(const char* what)
113
0
{
114
0
  (void)fprintf(stderr, "Invalid format string '%s'\n", what);
115
0
  return FALSE;
116
0
}
117
118
static BOOL check_and_log_format_size(char* format, size_t size, size_t index, size_t add)
119
31.5k
{
120
  /* format string must be '\0' terminated, so abort at size - 1 */
121
31.5k
  if (index + add + 1 >= size)
122
0
  {
123
0
    (void)fprintf(stderr,
124
0
                  "Format string too long ['%s', max %" PRIuz ", used %" PRIuz
125
0
                  ", adding %" PRIuz "]\n",
126
0
                  format, size, index, add);
127
0
    return FALSE;
128
0
  }
129
31.5k
  return TRUE;
130
31.5k
}
131
132
static int opt_compare_fn(const void* a, const void* b)
133
125k
{
134
125k
  const char* what = a;
135
125k
  const struct format_option* opt = b;
136
125k
  if (!opt)
137
0
    return -1;
138
125k
  return strncmp(what, opt->fmt, opt->fmtlen);
139
125k
}
140
141
static BOOL replace_format_string(const char* FormatString, struct format_option_recurse* recurse,
142
                                  char* format, size_t formatlen);
143
144
static const char* skip_if_null(const struct format_option* opt, const char* fmt,
145
                                size_t* preplacelen, size_t* pskiplen)
146
1.01k
{
147
1.01k
  WINPR_ASSERT(opt);
148
1.01k
  WINPR_ASSERT(fmt);
149
1.01k
  WINPR_ASSERT(preplacelen);
150
1.01k
  WINPR_ASSERT(pskiplen);
151
152
1.01k
  *preplacelen = 0;
153
1.01k
  *pskiplen = 0;
154
155
1.01k
  const char* str = &fmt[opt->fmtlen]; /* Skip first %{ from string */
156
1.01k
  const char* end = strstr(str, opt->replace);
157
1.01k
  if (!end)
158
0
    return NULL;
159
2.03k
  *pskiplen = WINPR_ASSERTING_INT_CAST(size_t, end - fmt) + opt->replacelen;
160
161
1.01k
  if (!opt->arg)
162
1.01k
    return NULL;
163
164
0
  const size_t replacelen = WINPR_ASSERTING_INT_CAST(size_t, end - str);
165
166
0
  char buffer[WLOG_MAX_PREFIX_SIZE] = { 0 };
167
0
  memcpy(buffer, str, MIN(replacelen, ARRAYSIZE(buffer) - 1));
168
169
0
  if (!replace_format_string(buffer, opt->recurse, opt->recurse->buffer,
170
0
                             ARRAYSIZE(opt->recurse->buffer)))
171
0
    return NULL;
172
173
0
  *preplacelen = strnlen(opt->recurse->buffer, ARRAYSIZE(opt->recurse->buffer));
174
0
  return opt->recurse->buffer;
175
0
}
176
177
static BOOL replace_format_string(const char* FormatString, struct format_option_recurse* recurse,
178
                                  char* format, size_t formatlen)
179
1.01k
{
180
1.01k
  WINPR_ASSERT(FormatString);
181
1.01k
  WINPR_ASSERT(recurse);
182
183
1.01k
  size_t index = 0;
184
185
32.5k
  while (*FormatString)
186
31.5k
  {
187
31.5k
    const struct format_option* opt =
188
31.5k
        bsearch(FormatString, recurse->options, recurse->nroptions,
189
31.5k
                sizeof(struct format_option), opt_compare_fn);
190
31.5k
    if (opt)
191
10.1k
    {
192
10.1k
      size_t replacelen = opt->replacelen;
193
10.1k
      size_t fmtlen = opt->fmtlen;
194
10.1k
      const char* replace = opt->replace;
195
10.1k
      const void* arg = opt->arg;
196
197
10.1k
      if (opt->ext)
198
1.01k
        replace = opt->ext(opt, FormatString, &replacelen, &fmtlen);
199
10.1k
      if (opt->fkt)
200
1.01k
        arg = opt->fkt(opt->arg);
201
202
10.1k
      if (replace && (replacelen > 0))
203
9.16k
      {
204
9.16k
        WINPR_PRAGMA_DIAG_PUSH
205
9.16k
        WINPR_PRAGMA_DIAG_IGNORED_FORMAT_NONLITERAL
206
9.16k
        const int rc = _snprintf(&format[index], formatlen - index, replace, arg);
207
9.16k
        WINPR_PRAGMA_DIAG_POP
208
9.16k
        if (rc < 0)
209
0
          return FALSE;
210
9.16k
        if (!check_and_log_format_size(format, formatlen, index,
211
18.3k
                                       WINPR_ASSERTING_INT_CAST(size_t, rc)))
212
0
          return FALSE;
213
18.3k
        index += WINPR_ASSERTING_INT_CAST(size_t, rc);
214
18.3k
      }
215
10.1k
      FormatString += fmtlen;
216
10.1k
    }
217
21.3k
    else
218
21.3k
    {
219
      /* Unknown format string */
220
21.3k
      if (*FormatString == '%')
221
0
        return log_invalid_fmt(FormatString);
222
223
21.3k
      if (!check_and_log_format_size(format, formatlen, index, 1))
224
0
        return FALSE;
225
21.3k
      format[index++] = *FormatString++;
226
21.3k
    }
227
31.5k
  }
228
229
1.01k
  if (!check_and_log_format_size(format, formatlen, index, 0))
230
0
    return FALSE;
231
1.01k
  return TRUE;
232
1.01k
}
233
234
BOOL WLog_Layout_GetMessagePrefix(wLog* log, wLogLayout* layout, wLogMessage* message)
235
1.01k
{
236
1.01k
  char format[WLOG_MAX_PREFIX_SIZE] = { 0 };
237
238
1.01k
  WINPR_ASSERT(layout);
239
1.01k
  WINPR_ASSERT(message);
240
241
1.01k
  struct format_tid_arg targ = { 0 };
242
243
1.01k
  SYSTEMTIME localTime = { 0 };
244
1.01k
  GetLocalTime(&localTime);
245
246
1.01k
  struct format_option_recurse recurse = {
247
1.01k
    .options = NULL, .nroptions = 0, .log = log, .layout = layout, .message = message
248
1.01k
  };
249
250
34.6k
#define ENTRY(x) x, sizeof(x) - 1
251
1.01k
  struct format_option options[] = {
252
1.01k
    { ENTRY("%ctx"), ENTRY("%s"), log->custom, log->context, NULL, &recurse }, /* log context */
253
1.01k
    { ENTRY("%dw"), ENTRY("%u"), NULL, (void*)(size_t)localTime.wDayOfWeek, NULL,
254
1.01k
      &recurse }, /* day of week */
255
1.01k
    { ENTRY("%dy"), ENTRY("%u"), NULL, (void*)(size_t)localTime.wDay, NULL,
256
1.01k
      &recurse }, /* day of year */
257
1.01k
    { ENTRY("%fl"), ENTRY("%s"), NULL, WINPR_CAST_CONST_PTR_AWAY(message->FileName, void*),
258
1.01k
      NULL, &recurse }, /* file */
259
1.01k
    { ENTRY("%fn"), ENTRY("%s"), NULL, WINPR_CAST_CONST_PTR_AWAY(message->FunctionName, void*),
260
1.01k
      NULL, &recurse }, /* function */
261
1.01k
    { ENTRY("%hr"), ENTRY("%02u"), NULL, (void*)(size_t)localTime.wHour, NULL,
262
1.01k
      &recurse }, /* hours */
263
1.01k
    { ENTRY("%ln"), ENTRY("%" PRIuz), NULL, (void*)message->LineNumber, NULL,
264
1.01k
      &recurse }, /* line number */
265
1.01k
    { ENTRY("%lv"), ENTRY("%s"), NULL,
266
1.01k
      WINPR_CAST_CONST_PTR_AWAY(WLOG_LEVELS[message->Level], void*), NULL,
267
1.01k
      &recurse }, /* log level */
268
1.01k
    { ENTRY("%mi"), ENTRY("%02u"), NULL, (void*)(size_t)localTime.wMinute, NULL,
269
1.01k
      &recurse }, /* minutes */
270
1.01k
    { ENTRY("%ml"), ENTRY("%03u"), NULL, (void*)(size_t)localTime.wMilliseconds, NULL,
271
1.01k
      &recurse },                                                   /* milliseconds */
272
1.01k
    { ENTRY("%mn"), ENTRY("%s"), NULL, log->Name, NULL, &recurse }, /* module name */
273
1.01k
    { ENTRY("%mo"), ENTRY("%u"), NULL, (void*)(size_t)localTime.wMonth, NULL,
274
1.01k
      &recurse }, /* month */
275
1.01k
    { ENTRY("%pid"), ENTRY("%u"), NULL, (void*)(size_t)GetCurrentProcessId(), NULL,
276
1.01k
      &recurse }, /* process id */
277
1.01k
    { ENTRY("%se"), ENTRY("%02u"), NULL, (void*)(size_t)localTime.wSecond, NULL,
278
1.01k
      &recurse },                                                   /* seconds */
279
1.01k
    { ENTRY("%tid"), ENTRY("%s"), get_tid, &targ, NULL, &recurse }, /* thread id */
280
1.01k
    { ENTRY("%yr"), ENTRY("%u"), NULL, (void*)(size_t)localTime.wYear, NULL,
281
1.01k
      &recurse }, /* year */
282
1.01k
    { ENTRY("%{"), ENTRY("%}"), NULL, log->context, skip_if_null,
283
1.01k
      &recurse }, /* skip if no context */
284
1.01k
  };
285
286
1.01k
  recurse.options = options;
287
1.01k
  recurse.nroptions = ARRAYSIZE(options);
288
289
1.01k
  if (!replace_format_string(layout->FormatString, &recurse, format, ARRAYSIZE(format)))
290
0
    return FALSE;
291
292
1.01k
  WINPR_PRAGMA_DIAG_PUSH
293
1.01k
  WINPR_PRAGMA_DIAG_IGNORED_FORMAT_SECURITY
294
295
1.01k
  WLog_PrintMessagePrefix(log, message, format);
296
297
1.01k
  WINPR_PRAGMA_DIAG_POP
298
299
1.01k
  return TRUE;
300
1.01k
}
301
302
wLogLayout* WLog_GetLogLayout(wLog* log)
303
0
{
304
0
  wLogAppender* appender = NULL;
305
0
  appender = WLog_GetLogAppender(log);
306
0
  return appender->Layout;
307
0
}
308
309
BOOL WLog_Layout_SetPrefixFormat(WINPR_ATTR_UNUSED wLog* log, wLogLayout* layout,
310
                                 const char* format)
311
0
{
312
0
  free(layout->FormatString);
313
0
  layout->FormatString = NULL;
314
315
0
  if (format)
316
0
  {
317
0
    layout->FormatString = _strdup(format);
318
319
0
    if (!layout->FormatString)
320
0
      return FALSE;
321
0
  }
322
323
0
  return TRUE;
324
0
}
325
326
wLogLayout* WLog_Layout_New(WINPR_ATTR_UNUSED wLog* log)
327
1
{
328
1
  LPCSTR prefix = "WLOG_PREFIX";
329
1
  DWORD nSize = 0;
330
1
  char* env = NULL;
331
1
  wLogLayout* layout = NULL;
332
1
  layout = (wLogLayout*)calloc(1, sizeof(wLogLayout));
333
334
1
  if (!layout)
335
0
    return NULL;
336
337
1
  nSize = GetEnvironmentVariableA(prefix, NULL, 0);
338
339
1
  if (nSize)
340
0
  {
341
0
    env = (LPSTR)malloc(nSize);
342
343
0
    if (!env)
344
0
    {
345
0
      free(layout);
346
0
      return NULL;
347
0
    }
348
349
0
    if (GetEnvironmentVariableA(prefix, env, nSize) != nSize - 1)
350
0
    {
351
0
      free(env);
352
0
      free(layout);
353
0
      return NULL;
354
0
    }
355
0
  }
356
357
1
  if (env)
358
0
    layout->FormatString = env;
359
1
  else
360
1
  {
361
#ifdef ANDROID
362
    layout->FormatString = _strdup("[pid=%pid:tid=%tid] - [%fn]%{[%ctx]%}: ");
363
#else
364
1
    layout->FormatString =
365
1
        _strdup("[%hr:%mi:%se:%ml] [%pid:%tid] [%lv][%mn] - [%fn]%{[%ctx]%}: ");
366
1
#endif
367
368
1
    if (!layout->FormatString)
369
0
    {
370
0
      free(layout);
371
0
      return NULL;
372
0
    }
373
1
  }
374
375
1
  return layout;
376
1
}
377
378
void WLog_Layout_Free(WINPR_ATTR_UNUSED wLog* log, wLogLayout* layout)
379
1
{
380
1
  if (layout)
381
1
  {
382
1
    if (layout->FormatString)
383
1
    {
384
1
      free(layout->FormatString);
385
1
      layout->FormatString = NULL;
386
1
    }
387
388
1
    free(layout);
389
1
  }
390
1
}