/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 | } |