1 /* SPDX-License-Identifier: LGPL-2.1+ */
18 #include "dirent-util.h"
22 #include "locale-util.h"
23 #include "path-util.h"
25 #include "string-table.h"
26 #include "string-util.h"
30 static int add_locales_from_archive(Set
*locales
) {
31 /* Stolen from glibc... */
37 /* Name hash table. */
38 uint32_t namehash_offset
;
39 uint32_t namehash_used
;
40 uint32_t namehash_size
;
42 uint32_t string_offset
;
45 /* Table with locale records. */
46 uint32_t locrectab_offset
;
47 uint32_t locrectab_used
;
48 uint32_t locrectab_size
;
49 /* MD5 sum hash table. */
50 uint32_t sumhash_offset
;
51 uint32_t sumhash_used
;
52 uint32_t sumhash_size
;
56 /* Hash value of the name. */
58 /* Offset of the name in the string table. */
60 /* Offset of the locale record. */
61 uint32_t locrec_offset
;
64 const struct locarhead
*h
;
65 const struct namehashent
*e
;
66 const void *p
= MAP_FAILED
;
67 _cleanup_close_
int fd
= -1;
73 fd
= open("/usr/lib/locale/locale-archive", O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
75 return errno
== ENOENT
? 0 : -errno
;
77 if (fstat(fd
, &st
) < 0)
80 if (!S_ISREG(st
.st_mode
))
83 if (st
.st_size
< (off_t
) sizeof(struct locarhead
))
86 p
= mmap(NULL
, st
.st_size
, PROT_READ
, MAP_SHARED
, fd
, 0);
90 h
= (const struct locarhead
*) p
;
91 if (h
->magic
!= 0xde020109 ||
92 h
->namehash_offset
+ h
->namehash_size
> st
.st_size
||
93 h
->string_offset
+ h
->string_size
> st
.st_size
||
94 h
->locrectab_offset
+ h
->locrectab_size
> st
.st_size
||
95 h
->sumhash_offset
+ h
->sumhash_size
> st
.st_size
) {
100 e
= (const struct namehashent
*) ((const uint8_t*) p
+ h
->namehash_offset
);
101 for (i
= 0; i
< h
->namehash_size
; i
++) {
104 if (e
[i
].locrec_offset
== 0)
107 if (!utf8_is_valid((char*) p
+ e
[i
].name_offset
))
110 z
= strdup((char*) p
+ e
[i
].name_offset
);
116 r
= set_consume(locales
, z
);
125 munmap((void*) p
, sz
);
130 static int add_locales_from_libdir (Set
*locales
) {
131 _cleanup_closedir_
DIR *dir
= NULL
;
132 struct dirent
*entry
;
135 dir
= opendir("/usr/lib/locale");
137 return errno
== ENOENT
? 0 : -errno
;
139 FOREACH_DIRENT(entry
, dir
, return -errno
) {
142 dirent_ensure_type(dir
, entry
);
144 if (entry
->d_type
!= DT_DIR
)
147 z
= strdup(entry
->d_name
);
151 r
= set_consume(locales
, z
);
152 if (r
< 0 && r
!= -EEXIST
)
159 int get_locales(char ***ret
) {
160 _cleanup_set_free_ Set
*locales
= NULL
;
161 _cleanup_strv_free_
char **l
= NULL
;
164 locales
= set_new(&string_hash_ops
);
168 r
= add_locales_from_archive(locales
);
169 if (r
< 0 && r
!= -ENOENT
)
172 r
= add_locales_from_libdir(locales
);
176 l
= set_get_strv(locales
);
187 bool locale_is_valid(const char *name
) {
192 if (strlen(name
) >= 128)
195 if (!utf8_is_valid(name
))
198 if (!filename_is_valid(name
))
201 if (!string_is_safe(name
))
207 void init_gettext(void) {
208 setlocale(LC_ALL
, "");
209 textdomain(GETTEXT_PACKAGE
);
212 bool is_locale_utf8(void) {
214 static int cached_answer
= -1;
216 /* Note that we default to 'true' here, since today UTF8 is
217 * pretty much supported everywhere. */
219 if (cached_answer
>= 0)
222 if (!setlocale(LC_ALL
, "")) {
223 cached_answer
= true;
227 set
= nl_langinfo(CODESET
);
229 cached_answer
= true;
233 if (streq(set
, "UTF-8")) {
234 cached_answer
= true;
238 /* For LC_CTYPE=="C" return true, because CTYPE is effectively
239 * unset and everything can do to UTF-8 nowadays. */
240 set
= setlocale(LC_CTYPE
, NULL
);
242 cached_answer
= true;
246 /* Check result, but ignore the result if C was set
249 STR_IN_SET(set
, "C", "POSIX") &&
251 !getenv("LC_CTYPE") &&
255 return (bool) cached_answer
;
258 static bool emoji_enabled(void) {
259 static int cached_emoji_enabled
= -1;
261 if (cached_emoji_enabled
< 0) {
264 val
= getenv_bool("SYSTEMD_EMOJI");
266 cached_emoji_enabled
=
268 !STRPTR_IN_SET(getenv("TERM"), "dumb", "linux");
270 cached_emoji_enabled
= val
;
273 return cached_emoji_enabled
;
276 const char *special_glyph(SpecialGlyph code
) {
278 /* A list of a number of interesting unicode glyphs we can use to decorate our output. It's probably wise to be
279 * conservative here, and primarily stick to the glyphs defined in the eurlatgr font, so that display still
280 * works reasonably well on the Linux console. For details see:
282 * http://git.altlinux.org/people/legion/packages/kbd.git?p=kbd.git;a=blob;f=data/consolefonts/README.eurlatgr
285 static const char* const draw_table
[2][_SPECIAL_GLYPH_MAX
] = {
288 [SPECIAL_GLYPH_TREE_VERTICAL
] = "| ",
289 [SPECIAL_GLYPH_TREE_BRANCH
] = "|-",
290 [SPECIAL_GLYPH_TREE_RIGHT
] = "`-",
291 [SPECIAL_GLYPH_TREE_SPACE
] = " ",
292 [SPECIAL_GLYPH_TRIANGULAR_BULLET
] = ">",
293 [SPECIAL_GLYPH_BLACK_CIRCLE
] = "*",
294 [SPECIAL_GLYPH_BULLET
] = "*",
295 [SPECIAL_GLYPH_ARROW
] = "->",
296 [SPECIAL_GLYPH_MDASH
] = "-",
297 [SPECIAL_GLYPH_ELLIPSIS
] = "...",
298 [SPECIAL_GLYPH_MU
] = "u",
299 [SPECIAL_GLYPH_CHECK_MARK
] = "+",
300 [SPECIAL_GLYPH_CROSS_MARK
] = "-",
301 [SPECIAL_GLYPH_ECSTATIC_SMILEY
] = ":-]",
302 [SPECIAL_GLYPH_HAPPY_SMILEY
] = ":-}",
303 [SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY
] = ":-)",
304 [SPECIAL_GLYPH_NEUTRAL_SMILEY
] = ":-|",
305 [SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY
] = ":-(",
306 [SPECIAL_GLYPH_UNHAPPY_SMILEY
] = ":-{️",
307 [SPECIAL_GLYPH_DEPRESSED_SMILEY
] = ":-[",
312 [SPECIAL_GLYPH_TREE_VERTICAL
] = "\342\224\202 ", /* │ */
313 [SPECIAL_GLYPH_TREE_BRANCH
] = "\342\224\234\342\224\200", /* ├─ */
314 [SPECIAL_GLYPH_TREE_RIGHT
] = "\342\224\224\342\224\200", /* └─ */
315 [SPECIAL_GLYPH_TREE_SPACE
] = " ", /* */
316 [SPECIAL_GLYPH_TRIANGULAR_BULLET
] = "\342\200\243", /* ‣ */
317 [SPECIAL_GLYPH_BLACK_CIRCLE
] = "\342\227\217", /* ● */
318 [SPECIAL_GLYPH_BULLET
] = "\342\200\242", /* • */
319 [SPECIAL_GLYPH_ARROW
] = "\342\206\222", /* → */
320 [SPECIAL_GLYPH_MDASH
] = "\342\200\223", /* – */
321 [SPECIAL_GLYPH_ELLIPSIS
] = "\342\200\246", /* … */
322 [SPECIAL_GLYPH_MU
] = "\316\274", /* μ */
323 [SPECIAL_GLYPH_CHECK_MARK
] = "\342\234\223", /* ✓ */
324 [SPECIAL_GLYPH_CROSS_MARK
] = "\342\234\227", /* ✗ */
325 [SPECIAL_GLYPH_ECSTATIC_SMILEY
] = "\360\237\230\207", /* 😇 */
326 [SPECIAL_GLYPH_HAPPY_SMILEY
] = "\360\237\230\200", /* 😀 */
327 [SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY
] = "\360\237\231\202", /* 🙂 */
328 [SPECIAL_GLYPH_NEUTRAL_SMILEY
] = "\360\237\230\220", /* 😐 */
329 [SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY
] = "\360\237\231\201", /* 🙁 */
330 [SPECIAL_GLYPH_UNHAPPY_SMILEY
] = "\360\237\230\250", /* 😨️️ */
331 [SPECIAL_GLYPH_DEPRESSED_SMILEY
] = "\360\237\244\242", /* 🤢 */
335 assert(code
< _SPECIAL_GLYPH_MAX
);
337 return draw_table
[code
>= _SPECIAL_GLYPH_FIRST_SMILEY
? emoji_enabled() : is_locale_utf8()][code
];
340 void locale_variables_free(char *l
[_VARIABLE_LC_MAX
]) {
346 for (i
= 0; i
< _VARIABLE_LC_MAX
; i
++)
350 static const char * const locale_variable_table
[_VARIABLE_LC_MAX
] = {
351 [VARIABLE_LANG
] = "LANG",
352 [VARIABLE_LANGUAGE
] = "LANGUAGE",
353 [VARIABLE_LC_CTYPE
] = "LC_CTYPE",
354 [VARIABLE_LC_NUMERIC
] = "LC_NUMERIC",
355 [VARIABLE_LC_TIME
] = "LC_TIME",
356 [VARIABLE_LC_COLLATE
] = "LC_COLLATE",
357 [VARIABLE_LC_MONETARY
] = "LC_MONETARY",
358 [VARIABLE_LC_MESSAGES
] = "LC_MESSAGES",
359 [VARIABLE_LC_PAPER
] = "LC_PAPER",
360 [VARIABLE_LC_NAME
] = "LC_NAME",
361 [VARIABLE_LC_ADDRESS
] = "LC_ADDRESS",
362 [VARIABLE_LC_TELEPHONE
] = "LC_TELEPHONE",
363 [VARIABLE_LC_MEASUREMENT
] = "LC_MEASUREMENT",
364 [VARIABLE_LC_IDENTIFICATION
] = "LC_IDENTIFICATION"
367 DEFINE_STRING_TABLE_LOOKUP(locale_variable
, LocaleVariable
);