Coverage Report

Created: 2025-06-24 06:40

/src/systemd/src/shared/locale-setup.c
Line
Count
Source (jump to first uncovered line)
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include <stdlib.h>
4
#include <sys/stat.h>
5
#include <unistd.h>
6
7
#include "alloc-util.h"
8
#include "env-file.h"
9
#include "env-util.h"
10
#include "errno-util.h"
11
#include "fd-util.h"
12
#include "locale-setup.h"
13
#include "log.h"
14
#include "proc-cmdline.h"
15
#include "stat-util.h"
16
#include "string-util.h"
17
#include "strv.h"
18
19
121k
void locale_context_clear(LocaleContext *c) {
20
121k
        assert(c);
21
22
121k
        c->st = (struct stat) {};
23
24
1.81M
        for (LocaleVariable i = 0; i < _VARIABLE_LC_MAX; i++)
25
1.69M
                c->locale[i] = mfree(c->locale[i]);
26
121k
}
27
28
40.4k
static int locale_context_load_proc(LocaleContext *c, LocaleLoadFlag flag) {
29
40.4k
        int r;
30
31
40.4k
        assert(c);
32
33
40.4k
        if (!FLAGS_SET(flag, LOCALE_LOAD_PROC_CMDLINE))
34
0
                return 0;
35
36
40.4k
        locale_context_clear(c);
37
38
40.4k
        r = proc_cmdline_get_key_many(PROC_CMDLINE_STRIP_RD_PREFIX,
39
40.4k
                                      "locale.LANG",              &c->locale[VARIABLE_LANG],
40
40.4k
                                      "locale.LANGUAGE",          &c->locale[VARIABLE_LANGUAGE],
41
40.4k
                                      "locale.LC_CTYPE",          &c->locale[VARIABLE_LC_CTYPE],
42
40.4k
                                      "locale.LC_NUMERIC",        &c->locale[VARIABLE_LC_NUMERIC],
43
40.4k
                                      "locale.LC_TIME",           &c->locale[VARIABLE_LC_TIME],
44
40.4k
                                      "locale.LC_COLLATE",        &c->locale[VARIABLE_LC_COLLATE],
45
40.4k
                                      "locale.LC_MONETARY",       &c->locale[VARIABLE_LC_MONETARY],
46
40.4k
                                      "locale.LC_MESSAGES",       &c->locale[VARIABLE_LC_MESSAGES],
47
40.4k
                                      "locale.LC_PAPER",          &c->locale[VARIABLE_LC_PAPER],
48
40.4k
                                      "locale.LC_NAME",           &c->locale[VARIABLE_LC_NAME],
49
40.4k
                                      "locale.LC_ADDRESS",        &c->locale[VARIABLE_LC_ADDRESS],
50
40.4k
                                      "locale.LC_TELEPHONE",      &c->locale[VARIABLE_LC_TELEPHONE],
51
40.4k
                                      "locale.LC_MEASUREMENT",    &c->locale[VARIABLE_LC_MEASUREMENT],
52
40.4k
                                      "locale.LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION]);
53
40.4k
        if (r == -ENOENT)
54
0
                return 0;
55
40.4k
        if (r < 0)
56
0
                return log_debug_errno(r, "Failed to read /proc/cmdline: %m");
57
40.4k
        return r;
58
40.4k
}
59
60
40.4k
static int locale_context_load_conf(LocaleContext *c, LocaleLoadFlag flag) {
61
40.4k
        _cleanup_close_ int fd = -EBADF;
62
40.4k
        struct stat st;
63
40.4k
        int r;
64
65
40.4k
        assert(c);
66
67
40.4k
        if (!FLAGS_SET(flag, LOCALE_LOAD_LOCALE_CONF))
68
0
                return 0;
69
70
40.4k
        fd = RET_NERRNO(open(etc_locale_conf(), O_CLOEXEC | O_PATH));
71
40.4k
        if (fd == -ENOENT)
72
40.4k
                return 0;
73
0
        if (fd < 0)
74
0
                return log_debug_errno(errno, "Failed to open %s: %m", "/etc/locale.conf");
75
76
0
        if (fstat(fd, &st) < 0)
77
0
                return log_debug_errno(errno, "Failed to stat /etc/locale.conf: %m");
78
79
        /* If the file is not changed, then we do not need to re-read the file. */
80
0
        if (stat_inode_unmodified(&c->st, &st))
81
0
                return 1; /* (already) loaded */
82
83
0
        c->st = st;
84
0
        locale_context_clear(c);
85
86
0
        r = parse_env_file_fd(fd, etc_locale_conf(),
87
0
                              "LANG",              &c->locale[VARIABLE_LANG],
88
0
                              "LANGUAGE",          &c->locale[VARIABLE_LANGUAGE],
89
0
                              "LC_CTYPE",          &c->locale[VARIABLE_LC_CTYPE],
90
0
                              "LC_NUMERIC",        &c->locale[VARIABLE_LC_NUMERIC],
91
0
                              "LC_TIME",           &c->locale[VARIABLE_LC_TIME],
92
0
                              "LC_COLLATE",        &c->locale[VARIABLE_LC_COLLATE],
93
0
                              "LC_MONETARY",       &c->locale[VARIABLE_LC_MONETARY],
94
0
                              "LC_MESSAGES",       &c->locale[VARIABLE_LC_MESSAGES],
95
0
                              "LC_PAPER",          &c->locale[VARIABLE_LC_PAPER],
96
0
                              "LC_NAME",           &c->locale[VARIABLE_LC_NAME],
97
0
                              "LC_ADDRESS",        &c->locale[VARIABLE_LC_ADDRESS],
98
0
                              "LC_TELEPHONE",      &c->locale[VARIABLE_LC_TELEPHONE],
99
0
                              "LC_MEASUREMENT",    &c->locale[VARIABLE_LC_MEASUREMENT],
100
0
                              "LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION]);
101
0
        if (r < 0)
102
0
                return log_debug_errno(r, "Failed to read /etc/locale.conf: %m");
103
104
0
        return 1; /* loaded */
105
0
}
106
107
40.4k
static int locale_context_load_env(LocaleContext *c, LocaleLoadFlag flag) {
108
40.4k
        int r;
109
110
40.4k
        assert(c);
111
112
40.4k
        if (!FLAGS_SET(flag, LOCALE_LOAD_ENVIRONMENT))
113
40.4k
                return 0;
114
115
0
        locale_context_clear(c);
116
117
        /* Fill in what we got passed from systemd. */
118
0
        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) {
119
0
                const char *name = ASSERT_PTR(locale_variable_to_string(p));
120
121
0
                r = free_and_strdup(&c->locale[p], empty_to_null(getenv(name)));
122
0
                if (r < 0)
123
0
                        return log_oom_debug();
124
0
        }
125
126
0
        return 1; /* loaded */
127
0
}
128
129
40.4k
int locale_context_load(LocaleContext *c, LocaleLoadFlag flag) {
130
40.4k
        int r;
131
132
40.4k
        assert(c);
133
134
40.4k
        r = locale_context_load_proc(c, flag);
135
40.4k
        if (r > 0)
136
0
                goto finalize;
137
138
40.4k
        r = locale_context_load_conf(c, flag);
139
40.4k
        if (r != 0)
140
0
                goto finalize;
141
142
40.4k
        r = locale_context_load_env(c, flag);
143
144
40.4k
finalize:
145
40.4k
        if (r <= 0) {
146
                /* Nothing loaded, or error. */
147
40.4k
                locale_context_clear(c);
148
40.4k
                return r;
149
40.4k
        }
150
151
0
        if (FLAGS_SET(flag, LOCALE_LOAD_SIMPLIFY))
152
0
                locale_variables_simplify(c->locale);
153
154
0
        return 0;
155
40.4k
}
156
157
40.4k
int locale_context_build_env(const LocaleContext *c, char ***ret_set, char ***ret_unset) {
158
40.4k
        _cleanup_strv_free_ char **set = NULL, **unset = NULL;
159
40.4k
        int r;
160
161
40.4k
        assert(c);
162
163
40.4k
        if (!ret_set && !ret_unset)
164
0
                return 0;
165
166
606k
        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) {
167
565k
                const char *name = ASSERT_PTR(locale_variable_to_string(p));
168
169
565k
                if (isempty(c->locale[p])) {
170
565k
                        if (!ret_unset)
171
565k
                                continue;
172
0
                        r = strv_extend(&unset, name);
173
0
                } else {
174
0
                        if (!ret_set)
175
0
                                continue;
176
0
                        r = strv_env_assign(&set, name, c->locale[p]);
177
0
                }
178
0
                if (r < 0)
179
0
                        return r;
180
0
        }
181
182
40.4k
        if (ret_set)
183
40.4k
                *ret_set = TAKE_PTR(set);
184
40.4k
        if (ret_unset)
185
0
                *ret_unset = TAKE_PTR(unset);
186
40.4k
        return 0;
187
40.4k
}
188
189
0
int locale_context_save(LocaleContext *c, char ***ret_set, char ***ret_unset) {
190
0
        _cleanup_strv_free_ char **set = NULL, **unset = NULL;
191
0
        int r;
192
193
0
        assert(c);
194
195
        /* Set values will be returned as strv in *ret on success. */
196
197
0
        r = locale_context_build_env(c, &set, ret_unset ? &unset : NULL);
198
0
        if (r < 0)
199
0
                return r;
200
201
0
        if (strv_isempty(set)) {
202
0
                if (unlink(etc_locale_conf()) < 0)
203
0
                        return errno == ENOENT ? 0 : -errno;
204
205
0
                c->st = (struct stat) {};
206
207
0
                if (ret_set)
208
0
                        *ret_set = NULL;
209
0
                if (ret_unset)
210
0
                        *ret_unset = NULL;
211
0
                return 0;
212
0
        }
213
214
0
        r = write_env_file(
215
0
                        AT_FDCWD,
216
0
                        etc_locale_conf(),
217
                        /* headers= */ NULL,
218
0
                        set,
219
0
                        WRITE_ENV_FILE_LABEL);
220
0
        if (r < 0)
221
0
                return r;
222
223
0
        if (stat(etc_locale_conf(), &c->st) < 0)
224
0
                return -errno;
225
226
0
        if (ret_set)
227
0
                *ret_set = TAKE_PTR(set);
228
0
        if (ret_unset)
229
0
                *ret_unset = TAKE_PTR(unset);
230
0
        return 0;
231
0
}
232
233
0
int locale_context_merge(const LocaleContext *c, char *l[_VARIABLE_LC_MAX]) {
234
0
        assert(c);
235
0
        assert(l);
236
237
0
        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++)
238
0
                if (!isempty(c->locale[p]) && isempty(l[p])) {
239
0
                        l[p] = strdup(c->locale[p]);
240
0
                        if (!l[p])
241
0
                                return -ENOMEM;
242
0
                }
243
244
0
        return 0;
245
0
}
246
247
0
void locale_context_take(LocaleContext *c, char *l[_VARIABLE_LC_MAX]) {
248
0
        assert(c);
249
0
        assert(l);
250
251
0
        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++)
252
0
                free_and_replace(c->locale[p], l[p]);
253
0
}
254
255
0
bool locale_context_equal(const LocaleContext *c, char *l[_VARIABLE_LC_MAX]) {
256
0
        assert(c);
257
0
        assert(l);
258
259
0
        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++)
260
0
                if (!streq_ptr(c->locale[p], l[p]))
261
0
                        return false;
262
263
0
        return true;
264
0
}
265
266
40.4k
int locale_setup(char ***environment) {
267
40.4k
        _cleanup_(locale_context_clear) LocaleContext c = {};
268
40.4k
        _cleanup_strv_free_ char **add = NULL;
269
40.4k
        int r;
270
271
40.4k
        assert(environment);
272
273
40.4k
        r = locale_context_load(&c, LOCALE_LOAD_PROC_CMDLINE | LOCALE_LOAD_LOCALE_CONF);
274
40.4k
        if (r < 0)
275
0
                return r;
276
277
40.4k
        r = locale_context_build_env(&c, &add, NULL);
278
40.4k
        if (r < 0)
279
0
                return r;
280
281
40.4k
        if (strv_isempty(add)) {
282
                /* If no locale is configured then default to compile-time default. */
283
284
40.4k
                add = strv_new("LANG=" SYSTEMD_DEFAULT_LOCALE);
285
40.4k
                if (!add)
286
0
                        return -ENOMEM;
287
40.4k
        }
288
289
40.4k
        if (strv_isempty(*environment))
290
40.4k
                strv_free_and_replace(*environment, add);
291
40.4k
        else {
292
40.4k
                char **merged;
293
294
40.4k
                merged = strv_env_merge(*environment, add);
295
40.4k
                if (!merged)
296
0
                        return -ENOMEM;
297
298
40.4k
                strv_free_and_replace(*environment, merged);
299
40.4k
        }
300
301
40.4k
        return 0;
302
40.4k
}
303
304
40.4k
const char* etc_locale_conf(void) {
305
40.4k
        static const char *cached = NULL;
306
307
40.4k
        if (!cached)
308
2
                cached = secure_getenv("SYSTEMD_ETC_LOCALE_CONF") ?: "/etc/locale.conf";
309
310
40.4k
        return cached;
311
40.4k
}
312
313
0
const char* etc_vconsole_conf(void) {
314
0
        static const char *cached = NULL;
315
316
0
        if (!cached)
317
0
                cached = secure_getenv("SYSTEMD_ETC_VCONSOLE_CONF") ?: "/etc/vconsole.conf";
318
319
0
        return cached;
320
0
}