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