Coverage Report

Created: 2025-08-29 07:18

/src/dovecot/src/lib/process-title.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "env-util.h"
5
#include "process-title.h"
6
7
#ifdef HAVE_LIBBSD
8
#include <bsd/unistd.h>
9
#else
10
#include <unistd.h> /* FreeBSD */
11
#endif
12
13
static char *process_name = NULL;
14
static char *current_process_title;
15
static unsigned int process_title_counter = 0;
16
17
#ifdef HAVE_SETPROCTITLE
18
#  undef PROCTITLE_HACK
19
#endif
20
21
#ifdef PROCTITLE_HACK
22
23
#ifdef DEBUG
24
/* if there are problems with this approach, try to make sure we notice it */
25
#  define PROCTITLE_CLEAR_CHAR 0xab
26
#else
27
/* There are always race conditions when updating the process title. ps might
28
   read a partially written title. Try to at least minimize this by using NUL
29
   as the fill character, so ps won't show a large number of 0xab chars. */
30
0
#  define PROCTITLE_CLEAR_CHAR 0
31
#endif
32
33
static char *process_title;
34
static size_t process_title_len, process_title_clean_pos;
35
static void *argv_memblock, *environ_memblock;
36
37
static void proctitle_hack_init(char *argv[], char *env[])
38
0
{
39
0
  char *last;
40
0
  unsigned int i;
41
0
  bool clear_env;
42
43
0
  i_assert(argv[0] != NULL);
44
45
  /* find the last argv or environment string. it should always be the
46
     last string in environ, but don't rely on it. this is what openssh
47
     does, so hopefully it's safe enough. */
48
0
  last = argv[0] + strlen(argv[0]) + 1;
49
0
  for (i = 1; argv[i] != NULL; i++) {
50
0
    if (argv[i] == last)
51
0
      last = argv[i] + strlen(argv[i]) + 1;
52
0
  }
53
0
  if (env[0] == NULL)
54
0
    clear_env = FALSE;
55
0
  else {
56
0
    clear_env = last == env[0];
57
0
    for (i = 0; env[i] != NULL; i++) {
58
0
      if (env[i] == last)
59
0
        last = env[i] + strlen(env[i]) + 1;
60
0
    }
61
0
  }
62
63
0
  process_title = argv[0];
64
0
  process_title_len = last - argv[0];
65
66
0
  if (clear_env) {
67
0
    memset(env[0], PROCTITLE_CLEAR_CHAR, last - env[0]);
68
0
    process_title_clean_pos = env[0] - process_title;
69
0
  } else {
70
0
    process_title_clean_pos = 0;
71
0
  }
72
0
}
73
74
static char **argv_dup(char *old_argv[], void **memblock_r)
75
0
{
76
  /* @UNSAFE */
77
0
  void *memblock, *memblock_end;
78
0
  char **new_argv;
79
0
  unsigned int i, count;
80
0
  size_t len, memblock_len = 0;
81
82
0
  for (count = 0; old_argv[count] != NULL; count++)
83
0
    memblock_len += strlen(old_argv[count]) + 1;
84
0
  memblock_len += sizeof(char *) * (count + 1);
85
86
0
  memblock = malloc(memblock_len);
87
0
  if (memblock == NULL)
88
0
    i_fatal_status(FATAL_OUTOFMEM, "malloc() failed: %m");
89
0
  *memblock_r = memblock;
90
0
  memblock_end = PTR_OFFSET(memblock, memblock_len);
91
92
0
  new_argv = memblock;
93
0
  memblock = PTR_OFFSET(memblock, sizeof(char *) * (count + 1));
94
95
0
  for (i = 0; i < count; i++) {
96
0
    new_argv[i] = memblock;
97
0
    len = strlen(old_argv[i]) + 1;
98
0
    memcpy(memblock, old_argv[i], len);
99
0
    memblock = PTR_OFFSET(memblock, len);
100
0
  }
101
0
  i_assert(memblock == memblock_end);
102
0
  new_argv[i] = NULL;
103
0
  return new_argv;
104
0
}
105
106
static void proctitle_hack_set(const char *title)
107
0
{
108
0
  size_t len = strlen(title);
109
110
  /* OS X wants two NULs */
111
0
  if (len >= process_title_len-1)
112
0
    len = process_title_len - 2;
113
114
0
  memcpy(process_title, title, len);
115
0
  process_title[len++] = '\0';
116
0
  process_title[len++] = '\0';
117
118
0
  if (len < process_title_clean_pos) {
119
0
    memset(process_title + len, PROCTITLE_CLEAR_CHAR,
120
0
           process_title_clean_pos - len);
121
0
    process_title_clean_pos = len;
122
0
  } else if (process_title_clean_pos != 0) {
123
0
    process_title_clean_pos = len;
124
0
  }
125
0
}
126
127
#endif
128
129
void process_title_init(int argc ATTR_UNUSED, char **argv[])
130
0
{
131
0
#ifdef PROCTITLE_HACK
132
0
  char ***environ_p = env_get_environ_p();
133
0
  char **orig_argv = *argv;
134
0
  char **orig_environ = *environ_p;
135
136
0
  *argv = argv_dup(orig_argv, &argv_memblock);
137
0
  *environ_p = argv_dup(orig_environ, &environ_memblock);
138
0
  proctitle_hack_init(orig_argv, orig_environ);
139
0
#endif
140
#ifdef HAVE_LIBBSD
141
  setproctitle_init(argc, *argv, *env_get_environ_p());
142
#endif
143
0
  process_name = (*argv)[0];
144
0
}
145
146
void process_title_set(const char *title)
147
0
{
148
0
  i_assert(process_name != NULL);
149
150
0
  process_title_counter++;
151
0
  i_free(current_process_title);
152
0
  current_process_title = i_strdup(title);
153
#ifdef HAVE_SETPROCTITLE
154
  if (title == NULL)
155
    setproctitle(NULL);
156
  else
157
    setproctitle("%s", title);
158
#elif defined(PROCTITLE_HACK)
159
0
  T_BEGIN {
160
0
    proctitle_hack_set(t_strconcat(process_name, " ", title, NULL));
161
0
  } T_END;
162
0
#endif
163
0
}
164
165
const char *process_title_get(void)
166
0
{
167
0
  return current_process_title;
168
0
}
169
170
unsigned int process_title_get_counter(void)
171
0
{
172
0
  return process_title_counter;
173
0
}
174
175
void process_title_deinit(void)
176
0
{
177
0
#ifdef PROCTITLE_HACK
178
0
  char ***environ_p = env_get_environ_p();
179
180
0
  free(argv_memblock);
181
0
  free(environ_memblock);
182
183
  /* Environment is no longer usable. Make sure we won't crash in case
184
     some library's deinit function still calls getenv(). This code was
185
     mainly added because of GNUTLS where we don't really care about the
186
     getenv() call.
187
188
     Alternatively we could remove the free() calls above, but that would
189
     annoy memory leak checking tools. Also we could attempt to restore
190
     the environ_p to its original state, but that's a bit complicated. */
191
0
  *environ_p = NULL;
192
0
#endif
193
0
  i_free(current_process_title);
194
0
}