diff --git a/src/plugins/fs/vfat.c b/src/plugins/fs/vfat.c index dd15e792..51f33241 100644 --- a/src/plugins/fs/vfat.c +++ b/src/plugins/fs/vfat.c @@ -276,43 +276,147 @@ gboolean bd_fs_vfat_repair (const gchar *device, const BDExtraArg **extra, GErro return ret; } +/** + * _vfat_locale_codepage: + * + * Determine the DOS/OEM codepage that matches the system locale, mirroring + * how Windows derives the OEM codepage from the system locale. FAT volume + * labels are stored as 8-bit OEM codepage bytes, so the codepage used to + * write a label must match the locale of the system that created it. + * + * The locale is read from the environment (LC_ALL > LC_CTYPE > LANG) rather + * than calling setlocale(), to avoid side effects on the host process + * (libblockdev runs inside udisksd). + * + * Returns: the codepage number, or 850 (the dosfstools default) if the locale + * cannot be determined or has no known mapping. + */ +static guint +_vfat_locale_codepage (void) { + const gchar *locale = g_getenv ("LC_ALL"); + if (locale == NULL || *locale == '\0') + locale = g_getenv ("LC_CTYPE"); + if (locale == NULL || *locale == '\0') + locale = g_getenv ("LANG"); + + if (locale == NULL || *locale == '\0' || + g_strcmp0 (locale, "C") == 0 || g_strcmp0 (locale, "POSIX") == 0 || + g_str_has_prefix (locale, "C.")) + return 850; + + /* Extract the language[_territory] part before '.' or '@' */ + g_autofree gchar *lang = g_strdup (locale); + gchar *p; + p = strchr (lang, '.'); + if (p) *p = '\0'; + p = strchr (lang, '@'); + if (p) *p = '\0'; + + if (g_str_has_prefix (lang, "zh_CN") || g_str_has_prefix (lang, "zh_SG")) + return 936; /* GBK */ + if (g_str_has_prefix (lang, "zh_TW") || g_str_has_prefix (lang, "zh_HK")) + return 950; /* Big5 */ + if (g_str_has_prefix (lang, "ja")) + return 932; /* Shift-JIS */ + if (g_str_has_prefix (lang, "ko")) + return 949; /* UHC (Korean) */ + if (g_str_has_prefix (lang, "ru") || g_str_has_prefix (lang, "uk") || + g_str_has_prefix (lang, "be")) + return 866; /* Cyrillic (DOS) */ + + return 850; +} + +/** + * _vfat_label_from_codepage: + * @label: (nullable): raw OEM codepage bytes from the filesystem + * + * Convert a label read from a VFAT filesystem (raw OEM codepage bytes, as + * returned by blkid) to UTF-8, using the locale-derived codepage. Pure + * ASCII labels and the default codepage (850) need no conversion. + * + * Returns: (transfer full): the UTF-8 label, or the original bytes on + * conversion failure. Never %NULL. + */ +static gchar * +_vfat_label_from_codepage (const gchar *label) { + if (label == NULL || *label == '\0') + return g_strdup (label ? label : ""); + + if (g_str_is_ascii (label)) + return g_strdup (label); + + guint cp = _vfat_locale_codepage (); + gchar cp_name[16]; + g_snprintf (cp_name, sizeof (cp_name), "CP%u", cp); + + GError *conv_error = NULL; + gchar *utf8 = g_convert (label, -1, "UTF-8", cp_name, NULL, NULL, &conv_error); + if (utf8 != NULL) + return utf8; + + /* Conversion failed; return a valid UTF-8 string instead of raw bytes + * that may not be valid UTF-8. */ + g_error_free (conv_error); + return g_utf8_make_valid (label, -1); +} + /** * bd_fs_vfat_set_label: * @device: the device containing the file system to set label for * @label: label to set * @error: (out) (optional): place to store error (if any) * - * Returns: whether the label of vfat file system on the @device was - * successfully set or not + * Sets the label of a VFAT filesystem. For labels containing non-ASCII + * characters, the OEM codepage matching the system locale is passed to + * fatlabel via the -c option, so that labels in CJK, Cyrillic and other + * non-Western-European scripts are encoded correctly. + * + * Returns: whether the label was successfully set or not * * Tech category: %BD_FS_TECH_VFAT-%BD_FS_TECH_MODE_SET_LABEL */ gboolean bd_fs_vfat_set_label (const gchar *device, const gchar *label, GError **error) { - const gchar *args[4] = {"fatlabel", device, NULL, NULL}; + /* "fatlabel" "-c" ""