• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 15263807472

26 May 2025 08:53PM UTC coverage: 72.046% (-0.002%) from 72.048%
15263807472

push

github

yuwata
src/core/manager.c: log preset activity on first boot

This gives us a little more information about what units were enabled
or disabled on that first boot and will be useful for OS developers
tracking down the source of unit state.

An example with this enabled looks like:

```
NET: Registered PF_VSOCK protocol family
systemd[1]: Applying preset policy.
systemd[1]: Unit /etc/systemd/system/dnsmasq.service is masked, ignoring.
systemd[1]: Unit /etc/systemd/system/systemd-repart.service is masked, ignoring.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket'.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir.mount' → '/etc/systemd/system/var-mnt-workdir.mount'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir\x2dtmp.mount' → '/etc/systemd/system/var-mnt-workdir\x2dtmp.mount'.
systemd[1]: Created symlink '/etc/systemd/system/afterburn-sshkeys.target.requires/afterburn-sshkeys@core.service' → '/usr/lib/systemd/system/afterburn-sshkeys@.service'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket' → '/usr/lib/systemd/system/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket' → '/usr/lib/systemd/system/systemd-resolved-monitor.socket'.
systemd[1]: Populated /etc with preset unit settings.
```

Considering it only happens on first boot and not on every boot I think
the extra information is worth the extra verbosity in the logs just for
that boot.

5 of 6 new or added lines in 1 file covered. (83.33%)

5463 existing lines in 165 files now uncovered.

299151 of 415222 relevant lines covered (72.05%)

702386.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.03
/src/basic/locale-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <langinfo.h>
5
#include <stdlib.h>
6
#include <sys/mman.h>
7
#include <sys/stat.h>
8

9
#include "dirent-util.h"
10
#include "env-util.h"
11
#include "fd-util.h"
12
#include "fileio.h"
13
#include "locale-util.h"
14
#include "log.h"
15
#include "path-util.h"
16
#include "process-util.h"
17
#include "set.h"
18
#include "string-table.h"
19
#include "string-util.h"
20
#include "strv.h"
21
#include "utf8.h"
22

23
static char* normalize_locale(const char *name) {
21✔
24
        const char *e;
21✔
25

26
        /* Locale names are weird: glibc has some magic rules when looking for the charset name on disk: it
27
         * lowercases everything, and removes most special chars. This means the official .UTF-8 suffix
28
         * becomes .utf8 when looking things up on disk. When enumerating locales, let's do the reverse
29
         * operation, and go back to ".UTF-8" which appears to be the more commonly accepted name. We only do
30
         * that for UTF-8 however, since it's kinda the only charset that matters. */
31

32
        e = endswith(name, ".utf8");
21✔
33
        if (e) {
21✔
34
                _cleanup_free_ char *prefix = NULL;
16✔
35

36
                prefix = strndup(name, e - name);
16✔
37
                if (!prefix)
16✔
38
                        return NULL;
39

40
                return strjoin(prefix, ".UTF-8");
16✔
41
        }
42

43
        e = strstr(name, ".utf8@");
5✔
44
        if (e) {
5✔
UNCOV
45
                _cleanup_free_ char *prefix = NULL;
×
46

UNCOV
47
                prefix = strndup(name, e - name);
×
UNCOV
48
                if (!prefix)
×
49
                        return NULL;
50

51
                return strjoin(prefix, ".UTF-8@", e + 6);
×
52
        }
53

54
        return strdup(name);
5✔
55
}
56

57
static int add_locales_from_archive(Set *locales) {
9✔
58
        /* Stolen from glibc... */
59

60
        struct locarhead {
9✔
61
                uint32_t magic;
62
                /* Serial number.  */
63
                uint32_t serial;
64
                /* Name hash table.  */
65
                uint32_t namehash_offset;
66
                uint32_t namehash_used;
67
                uint32_t namehash_size;
68
                /* String table.  */
69
                uint32_t string_offset;
70
                uint32_t string_used;
71
                uint32_t string_size;
72
                /* Table with locale records.  */
73
                uint32_t locrectab_offset;
74
                uint32_t locrectab_used;
75
                uint32_t locrectab_size;
76
                /* MD5 sum hash table.  */
77
                uint32_t sumhash_offset;
78
                uint32_t sumhash_used;
79
                uint32_t sumhash_size;
80
        };
81

82
        struct namehashent {
9✔
83
                /* Hash value of the name.  */
84
                uint32_t hashval;
85
                /* Offset of the name in the string table.  */
86
                uint32_t name_offset;
87
                /* Offset of the locale record.  */
88
                uint32_t locrec_offset;
89
        };
90

91
        int r;
9✔
92

93
        assert(locales);
9✔
94

95
        _cleanup_close_ int fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
18✔
96
        if (fd < 0)
9✔
97
                return errno == ENOENT ? 0 : -errno;
3✔
98

99
        struct stat st;
6✔
100
        if (fstat(fd, &st) < 0)
6✔
UNCOV
101
                return -errno;
×
102

103
        if (!S_ISREG(st.st_mode))
6✔
104
                return -EBADMSG;
105

106
        if (st.st_size < (off_t) sizeof(struct locarhead))
6✔
107
                return -EBADMSG;
108

109
        if (file_offset_beyond_memory_size(st.st_size))
6✔
110
                return -EFBIG;
111

112
        void *p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
6✔
113
        if (p == MAP_FAILED)
6✔
UNCOV
114
                return -errno;
×
115

116
        const struct namehashent *e;
6✔
117
        const struct locarhead *h = p;
6✔
118
        if (h->magic != 0xde020109 ||
6✔
119
            h->namehash_offset + h->namehash_size > st.st_size ||
6✔
120
            h->string_offset + h->string_size > st.st_size ||
6✔
121
            h->locrectab_offset + h->locrectab_size > st.st_size ||
6✔
122
            h->sumhash_offset + h->sumhash_size > st.st_size) {
6✔
UNCOV
123
                r = -EBADMSG;
×
UNCOV
124
                goto finish;
×
125
        }
126

127
        e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
6✔
128
        for (size_t i = 0; i < h->namehash_size; i++) {
5,448✔
129
                char *z;
5,442✔
130

131
                if (e[i].locrec_offset == 0)
5,442✔
132
                        continue;
5,435✔
133

134
                if (!utf8_is_valid((char*) p + e[i].name_offset))
7✔
UNCOV
135
                        continue;
×
136

137
                z = normalize_locale((char*) p + e[i].name_offset);
7✔
138
                if (!z) {
7✔
UNCOV
139
                        r = -ENOMEM;
×
UNCOV
140
                        goto finish;
×
141
                }
142

143
                r = set_consume(locales, z);
7✔
144
                if (r < 0)
7✔
145
                        goto finish;
×
146
        }
147

148
        r = 0;
149

150
finish:
6✔
151
        if (p != MAP_FAILED)
6✔
152
                munmap((void*) p, st.st_size);
6✔
153

154
        return r;
6✔
155
}
156

157
static int add_locales_from_libdir(Set *locales) {
9✔
158
        _cleanup_closedir_ DIR *dir = NULL;
9✔
159
        int r;
9✔
160

161
        assert(locales);
9✔
162

163
        dir = opendir("/usr/lib/locale");
9✔
164
        if (!dir)
9✔
UNCOV
165
                return errno == ENOENT ? 0 : -errno;
×
166

167
        FOREACH_DIRENT(de, dir, return -errno) {
47✔
168
                char *z;
20✔
169

170
                if (de->d_type != DT_DIR)
20✔
171
                        continue;
6✔
172

173
                z = normalize_locale(de->d_name);
14✔
174
                if (!z)
14✔
175
                        return -ENOMEM;
176

177
                r = set_consume(locales, z);
14✔
178
                if (r < 0)
14✔
179
                        return r;
180
        }
181

182
        return 0;
183
}
184

185
int get_locales(char ***ret) {
9✔
186
        _cleanup_set_free_ Set *locales = NULL;
9✔
187
        int r;
9✔
188

189
        locales = set_new(&string_hash_ops_free);
9✔
190
        if (!locales)
9✔
191
                return -ENOMEM;
192

193
        r = add_locales_from_archive(locales);
9✔
194
        if (r < 0 && r != -ENOENT)
9✔
195
                return r;
196

197
        r = add_locales_from_libdir(locales);
9✔
198
        if (r < 0)
9✔
199
                return r;
200

201
        char *locale;
9✔
202
        SET_FOREACH(locale, locales) {
39✔
203
                r = locale_is_installed(locale);
21✔
204
                if (r < 0)
21✔
UNCOV
205
                        return r;
×
206
                if (r == 0)
21✔
207
                        free(set_remove(locales, locale));
5✔
208
        }
209

210
        _cleanup_strv_free_ char **l = set_to_strv(&locales);
18✔
211
        if (!l)
9✔
212
                return -ENOMEM;
213

214
        r = getenv_bool("SYSTEMD_LIST_NON_UTF8_LOCALES");
9✔
215
        if (r <= 0) {
9✔
216
                if (!IN_SET(r, -ENXIO, 0))
9✔
UNCOV
217
                        log_debug_errno(r, "Failed to parse $SYSTEMD_LIST_NON_UTF8_LOCALES as boolean, ignoring: %m");
×
218

219
                /* Filter out non-UTF-8 locales, because it's 2019, by default */
220
                char **b = l;
9✔
221
                STRV_FOREACH(a, l)
25✔
222
                        if (endswith(*a, "UTF-8") || strstr(*a, ".UTF-8@"))
16✔
223
                                *(b++) = *a;
16✔
224
                        else
UNCOV
225
                                free(*a);
×
226

227
                *b = NULL;
9✔
228
        }
229

230
        strv_sort(l);
9✔
231

232
        *ret = TAKE_PTR(l);
9✔
233

234
        return 0;
9✔
235
}
236

237
bool locale_is_valid(const char *name) {
73✔
238

239
        if (isempty(name))
73✔
240
                return false;
241

242
        if (strlen(name) >= 128)
71✔
243
                return false;
244

245
        if (!utf8_is_valid(name))
71✔
246
                return false;
247

248
        if (!filename_is_valid(name))
71✔
249
                return false;
250

251
        /* Locales look like: ll_CC.ENC@variant, where ll and CC are alphabetic, ENC is alphanumeric with
252
         * dashes, and variant seems to be alphabetic.
253
         * See: https://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html */
254
        if (!in_charset(name, ALPHANUMERICAL "_.-@"))
69✔
255
                return false;
2✔
256

257
        return true;
258
}
259

260
int locale_is_installed(const char *name) {
42✔
261
        if (!locale_is_valid(name))
42✔
262
                return false;
42✔
263

264
        if (STR_IN_SET(name, "C", "POSIX")) /* These ones are always OK */
39✔
265
                return true;
5✔
266

267
        _cleanup_(freelocalep) locale_t loc = newlocale(LC_ALL_MASK, name, (locale_t) 0);
34✔
268
        if (loc == (locale_t) 0)
34✔
269
                return errno == ENOMEM ? -ENOMEM : false;
12✔
270

271
        return true;
272
}
273

274
static bool is_locale_utf8_impl(void) {
12,409✔
275
        const char *set;
12,409✔
276
        int r;
12,409✔
277

278
        /* Note that we default to 'true' here, since today UTF8 is pretty much supported everywhere. */
279

280
        r = secure_getenv_bool("SYSTEMD_UTF8");
12,409✔
281
        if (r >= 0)
12,409✔
282
                return r;
94✔
283
        if (r != -ENXIO)
12,315✔
UNCOV
284
                log_debug_errno(r, "Failed to parse $SYSTEMD_UTF8, ignoring: %m");
×
285

286
        /* This function may be called from libsystemd, and setlocale() is not thread safe. Assuming yes. */
287
        if (!is_main_thread())
12,315✔
288
                return true;
289

290
        if (!setlocale(LC_ALL, ""))
12,293✔
291
                return true;
292

293
        set = nl_langinfo(CODESET);
12,293✔
294
        if (!set || streq(set, "UTF-8"))
12,293✔
295
                return true;
296

297
        set = setlocale(LC_CTYPE, NULL);
5,163✔
298
        if (!set)
5,163✔
299
                return true;
300

301
        /* Unless LC_CTYPE is explicitly overridden, return true. Because here CTYPE is effectively unset
302
         * and everything can do to UTF-8 nowadays. */
303
        return STR_IN_SET(set, "C", "POSIX") &&
10,326✔
304
                !getenv("LC_ALL") &&
10,326✔
305
                !getenv("LC_CTYPE") &&
15,489✔
306
                !getenv("LANG");
5,162✔
307
}
308

309
bool is_locale_utf8(void) {
892,419✔
310
        static int cached = -1;
892,419✔
311

312
        if (cached < 0)
892,419✔
313
                cached = is_locale_utf8_impl();
12,409✔
314

315
        return cached;
892,419✔
316
}
317

318
void locale_variables_free(char *l[_VARIABLE_LC_MAX]) {
8✔
319
        free_many_charp(l, _VARIABLE_LC_MAX);
8✔
320
}
8✔
321

322
void locale_variables_simplify(char *l[_VARIABLE_LC_MAX]) {
278✔
323
        assert(l);
278✔
324

325
        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) {
4,170✔
326
                if (p == VARIABLE_LANG)
3,892✔
327
                        continue;
278✔
328
                if (isempty(l[p]) || streq_ptr(l[VARIABLE_LANG], l[p]))
3,629✔
329
                        l[p] = mfree(l[p]);
3,601✔
330
        }
331
}
278✔
332

333
static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
334
        [VARIABLE_LANG]              = "LANG",
335
        [VARIABLE_LANGUAGE]          = "LANGUAGE",
336
        [VARIABLE_LC_CTYPE]          = "LC_CTYPE",
337
        [VARIABLE_LC_NUMERIC]        = "LC_NUMERIC",
338
        [VARIABLE_LC_TIME]           = "LC_TIME",
339
        [VARIABLE_LC_COLLATE]        = "LC_COLLATE",
340
        [VARIABLE_LC_MONETARY]       = "LC_MONETARY",
341
        [VARIABLE_LC_MESSAGES]       = "LC_MESSAGES",
342
        [VARIABLE_LC_PAPER]          = "LC_PAPER",
343
        [VARIABLE_LC_NAME]           = "LC_NAME",
344
        [VARIABLE_LC_ADDRESS]        = "LC_ADDRESS",
345
        [VARIABLE_LC_TELEPHONE]      = "LC_TELEPHONE",
346
        [VARIABLE_LC_MEASUREMENT]    = "LC_MEASUREMENT",
347
        [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
348
};
349

350
DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);
15,724✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc