// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ #include #include #include "exfat.h" static u16 bad_dos_chars[] = { /* + , ; = [ ] */ 0x002B, 0x002C, 0x003B, 0x003D, 0x005B, 0x005D, 0xFF0B, 0xFF0C, 0xFF1B, 0xFF1D, 0xFF3B, 0xFF3D, 0 }; static u16 bad_uni_chars[] = { /* " * / : < > ? \ | */ 0x0022, 0x002A, 0x002F, 0x003A, 0x003C, 0x003E, 0x003F, 0x005C, 0x007C, 0 }; static int convert_ch_to_uni(struct nls_table *nls, u16 *uni, u8 *ch, bool *lossy) { int len; *uni = 0x0; if (ch[0] < 0x80) { *uni = (u16)ch[0]; return 1; } len = nls->char2uni(ch, NLS_MAX_CHARSET_SIZE, uni); if (len < 0) { /* conversion failed */ pr_info("%s: fail to use nls\n", __func__); if (lossy) *lossy = true; *uni = (u16)'_'; if (!strcmp(nls->charset, "utf8")) return 1; else return 2; } return len; } static int convert_uni_to_ch(struct nls_table *nls, u8 *ch, u16 uni, bool *lossy) { int len; ch[0] = 0x0; if (uni < 0x0080) { ch[0] = (u8)uni; return 1; } len = nls->uni2char(uni, ch, NLS_MAX_CHARSET_SIZE); if (len < 0) { /* conversion failed */ pr_info("%s: fail to use nls\n", __func__); if (lossy) *lossy = true; ch[0] = '_'; return 1; } return len; } u16 nls_upper(struct super_block *sb, u16 a) { struct fs_info_t *p_fs = &(EXFAT_SB(sb)->fs_info); if (EXFAT_SB(sb)->options.casesensitive) return a; if (p_fs->vol_utbl && p_fs->vol_utbl[get_col_index(a)]) return p_fs->vol_utbl[get_col_index(a)][get_row_index(a)]; else return a; } static u16 *nls_wstrchr(u16 *str, u16 wchar) { while (*str) { if (*(str++) == wchar) return str; } return NULL; } int nls_dosname_cmp(struct super_block *sb, u8 *a, u8 *b) { return strncmp(a, b, DOS_NAME_LENGTH); } int nls_uniname_cmp(struct super_block *sb, u16 *a, u16 *b) { int i; for (i = 0; i < MAX_NAME_LENGTH; i++, a++, b++) { if (nls_upper(sb, *a) != nls_upper(sb, *b)) return 1; if (*a == 0x0) return 0; } return 0; } void nls_uniname_to_dosname(struct super_block *sb, struct dos_name_t *p_dosname, struct uni_name_t *p_uniname, bool *p_lossy) { int i, j, len; bool lossy = false; u8 buf[MAX_CHARSET_SIZE]; u8 lower = 0, upper = 0; u8 *dosname = p_dosname->name; u16 *uniname = p_uniname->name; u16 *p, *last_period; struct nls_table *nls = EXFAT_SB(sb)->nls_disk; for (i = 0; i < DOS_NAME_LENGTH; i++) *(dosname + i) = ' '; if (!nls_uniname_cmp(sb, uniname, (u16 *)UNI_CUR_DIR_NAME)) { *(dosname) = '.'; p_dosname->name_case = 0x0; if (p_lossy) *p_lossy = false; return; } if (!nls_uniname_cmp(sb, uniname, (u16 *)UNI_PAR_DIR_NAME)) { *(dosname) = '.'; *(dosname + 1) = '.'; p_dosname->name_case = 0x0; if (p_lossy) *p_lossy = false; return; } /* search for the last embedded period */ last_period = NULL; for (p = uniname; *p; p++) { if (*p == (u16)'.') last_period = p; } i = 0; while (i < DOS_NAME_LENGTH) { if (i == 8) { if (!last_period) break; if (uniname <= last_period) { if (uniname < last_period) lossy = true; uniname = last_period + 1; } } if (*uniname == (u16)'\0') { break; } else if (*uniname == (u16)' ') { lossy = true; } else if (*uniname == (u16)'.') { if (uniname < last_period) lossy = true; else i = 8; } else if (nls_wstrchr(bad_dos_chars, *uniname)) { lossy = true; *(dosname + i) = '_'; i++; } else { len = convert_uni_to_ch(nls, buf, *uniname, &lossy); if (len > 1) { if ((i >= 8) && ((i + len) > DOS_NAME_LENGTH)) break; if ((i < 8) && ((i + len) > 8)) { i = 8; continue; } lower = 0xFF; for (j = 0; j < len; j++, i++) *(dosname + i) = *(buf + j); } else { /* len == 1 */ if ((*buf >= 'a') && (*buf <= 'z')) { *(dosname + i) = *buf - ('a' - 'A'); if (i < 8) lower |= 0x08; else lower |= 0x10; } else if ((*buf >= 'A') && (*buf <= 'Z')) { *(dosname + i) = *buf; if (i < 8) upper |= 0x08; else upper |= 0x10; } else { *(dosname + i) = *buf; } i++; } } uniname++; } if (*dosname == 0xE5) *dosname = 0x05; if (*uniname != 0x0) lossy = true; if (upper & lower) p_dosname->name_case = 0xFF; else p_dosname->name_case = lower; if (p_lossy) *p_lossy = lossy; } void nls_dosname_to_uniname(struct super_block *sb, struct uni_name_t *p_uniname, struct dos_name_t *p_dosname) { int i = 0, j, n = 0; u8 buf[DOS_NAME_LENGTH + 2]; u8 *dosname = p_dosname->name; u16 *uniname = p_uniname->name; struct nls_table *nls = EXFAT_SB(sb)->nls_disk; if (*dosname == 0x05) { *buf = 0xE5; i++; n++; } for (; i < 8; i++, n++) { if (*(dosname + i) == ' ') break; if ((*(dosname + i) >= 'A') && (*(dosname + i) <= 'Z') && (p_dosname->name_case & 0x08)) *(buf + n) = *(dosname + i) + ('a' - 'A'); else *(buf + n) = *(dosname + i); } if (*(dosname + 8) != ' ') { *(buf + n) = '.'; n++; } for (i = 8; i < DOS_NAME_LENGTH; i++, n++) { if (*(dosname + i) == ' ') break; if ((*(dosname + i) >= 'A') && (*(dosname + i) <= 'Z') && (p_dosname->name_case & 0x10)) *(buf + n) = *(dosname + i) + ('a' - 'A'); else *(buf + n) = *(dosname + i); } *(buf + n) = '\0'; i = 0; j = 0; while (j < (MAX_NAME_LENGTH - 1)) { if (*(buf + i) == '\0') break; i += convert_ch_to_uni(nls, uniname, (buf + i), NULL); uniname++; j++; } *uniname = (u16)'\0'; } void nls_uniname_to_cstring(struct super_block *sb, u8 *p_cstring, struct uni_name_t *p_uniname) { int i, j, len; u8 buf[MAX_CHARSET_SIZE]; u16 *uniname = p_uniname->name; struct nls_table *nls = EXFAT_SB(sb)->nls_io; if (!nls) { len = utf16s_to_utf8s(uniname, MAX_NAME_LENGTH, UTF16_HOST_ENDIAN, p_cstring, MAX_NAME_LENGTH); p_cstring[len] = 0; return; } i = 0; while (i < (MAX_NAME_LENGTH - 1)) { if (*uniname == (u16)'\0') break; len = convert_uni_to_ch(nls, buf, *uniname, NULL); if (len > 1) { for (j = 0; j < len; j++) *p_cstring++ = (char)*(buf + j); } else { /* len == 1 */ *p_cstring++ = (char)*buf; } uniname++; i++; } *p_cstring = '\0'; } void nls_cstring_to_uniname(struct super_block *sb, struct uni_name_t *p_uniname, u8 *p_cstring, bool *p_lossy) { int i, j; bool lossy = false; u8 *end_of_name; u8 upname[MAX_NAME_LENGTH * 2]; u16 *uniname = p_uniname->name; struct nls_table *nls = EXFAT_SB(sb)->nls_io; /* strip all trailing spaces */ end_of_name = p_cstring + strlen(p_cstring); while (*(--end_of_name) == ' ') { if (end_of_name < p_cstring) break; } *(++end_of_name) = '\0'; if (strcmp(p_cstring, ".") && strcmp(p_cstring, "..")) { /* strip all trailing periods */ while (*(--end_of_name) == '.') { if (end_of_name < p_cstring) break; } *(++end_of_name) = '\0'; } if (*p_cstring == '\0') lossy = true; if (!nls) { i = utf8s_to_utf16s(p_cstring, MAX_NAME_LENGTH, UTF16_HOST_ENDIAN, uniname, MAX_NAME_LENGTH); for (j = 0; j < i; j++) SET16_A(upname + j * 2, nls_upper(sb, uniname[j])); uniname[i] = '\0'; } else { i = 0; j = 0; while (j < (MAX_NAME_LENGTH - 1)) { if (*(p_cstring + i) == '\0') break; i += convert_ch_to_uni(nls, uniname, (u8 *)(p_cstring + i), &lossy); if ((*uniname < 0x0020) || nls_wstrchr(bad_uni_chars, *uniname)) lossy = true; SET16_A(upname + j * 2, nls_upper(sb, *uniname)); uniname++; j++; } if (*(p_cstring + i) != '\0') lossy = true; *uniname = (u16)'\0'; } p_uniname->name_len = j; p_uniname->name_hash = calc_checksum_2byte(upname, j << 1, 0, CS_DEFAULT); if (p_lossy) *p_lossy = lossy; }