Linux-libre 5.4.48-gnu
[librecmc/linux-libre.git] / drivers / staging / exfat / exfat_nls.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *  Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
4  */
5
6 #include <linux/string.h>
7 #include <linux/nls.h>
8 #include "exfat.h"
9
10 static u16 bad_dos_chars[] = {
11         /* + , ; = [ ] */
12         0x002B, 0x002C, 0x003B, 0x003D, 0x005B, 0x005D,
13         0xFF0B, 0xFF0C, 0xFF1B, 0xFF1D, 0xFF3B, 0xFF3D,
14         0
15 };
16
17 static u16 bad_uni_chars[] = {
18         /* " * / : < > ? \ | */
19         0x0022,         0x002A, 0x002F, 0x003A,
20         0x003C, 0x003E, 0x003F, 0x005C, 0x007C,
21         0
22 };
23
24 static int convert_ch_to_uni(struct nls_table *nls, u16 *uni, u8 *ch,
25                              bool *lossy)
26 {
27         int len;
28
29         *uni = 0x0;
30
31         if (ch[0] < 0x80) {
32                 *uni = (u16)ch[0];
33                 return 1;
34         }
35
36         len = nls->char2uni(ch, NLS_MAX_CHARSET_SIZE, uni);
37         if (len < 0) {
38                 /* conversion failed */
39                 pr_info("%s: fail to use nls\n", __func__);
40                 if (lossy)
41                         *lossy = true;
42                 *uni = (u16)'_';
43                 if (!strcmp(nls->charset, "utf8"))
44                         return 1;
45                 else
46                         return 2;
47         }
48
49         return len;
50 }
51
52 static int convert_uni_to_ch(struct nls_table *nls, u8 *ch, u16 uni,
53                              bool *lossy)
54 {
55         int len;
56
57         ch[0] = 0x0;
58
59         if (uni < 0x0080) {
60                 ch[0] = (u8)uni;
61                 return 1;
62         }
63
64         len = nls->uni2char(uni, ch, NLS_MAX_CHARSET_SIZE);
65         if (len < 0) {
66                 /* conversion failed */
67                 pr_info("%s: fail to use nls\n", __func__);
68                 if (lossy)
69                         *lossy = true;
70                 ch[0] = '_';
71                 return 1;
72         }
73
74         return len;
75 }
76
77 u16 nls_upper(struct super_block *sb, u16 a)
78 {
79         struct fs_info_t *p_fs = &(EXFAT_SB(sb)->fs_info);
80
81         if (EXFAT_SB(sb)->options.casesensitive)
82                 return a;
83         if (p_fs->vol_utbl && p_fs->vol_utbl[get_col_index(a)])
84                 return p_fs->vol_utbl[get_col_index(a)][get_row_index(a)];
85         else
86                 return a;
87 }
88
89 static u16 *nls_wstrchr(u16 *str, u16 wchar)
90 {
91         while (*str) {
92                 if (*(str++) == wchar)
93                         return str;
94         }
95
96         return NULL;
97 }
98
99 int nls_dosname_cmp(struct super_block *sb, u8 *a, u8 *b)
100 {
101         return strncmp(a, b, DOS_NAME_LENGTH);
102 }
103
104 int nls_uniname_cmp(struct super_block *sb, u16 *a, u16 *b)
105 {
106         int i;
107
108         for (i = 0; i < MAX_NAME_LENGTH; i++, a++, b++) {
109                 if (nls_upper(sb, *a) != nls_upper(sb, *b))
110                         return 1;
111                 if (*a == 0x0)
112                         return 0;
113         }
114         return 0;
115 }
116
117 void nls_uniname_to_dosname(struct super_block *sb,
118                             struct dos_name_t *p_dosname,
119                             struct uni_name_t *p_uniname, bool *p_lossy)
120 {
121         int i, j, len;
122         bool lossy = false;
123         u8 buf[MAX_CHARSET_SIZE];
124         u8 lower = 0, upper = 0;
125         u8 *dosname = p_dosname->name;
126         u16 *uniname = p_uniname->name;
127         u16 *p, *last_period;
128         struct nls_table *nls = EXFAT_SB(sb)->nls_disk;
129
130         for (i = 0; i < DOS_NAME_LENGTH; i++)
131                 *(dosname + i) = ' ';
132
133         if (!nls_uniname_cmp(sb, uniname, (u16 *)UNI_CUR_DIR_NAME)) {
134                 *(dosname) = '.';
135                 p_dosname->name_case = 0x0;
136                 if (p_lossy)
137                         *p_lossy = false;
138                 return;
139         }
140
141         if (!nls_uniname_cmp(sb, uniname, (u16 *)UNI_PAR_DIR_NAME)) {
142                 *(dosname) = '.';
143                 *(dosname + 1) = '.';
144                 p_dosname->name_case = 0x0;
145                 if (p_lossy)
146                         *p_lossy = false;
147                 return;
148         }
149
150         /* search for the last embedded period */
151         last_period = NULL;
152         for (p = uniname; *p; p++) {
153                 if (*p == (u16)'.')
154                         last_period = p;
155         }
156
157         i = 0;
158         while (i < DOS_NAME_LENGTH) {
159                 if (i == 8) {
160                         if (!last_period)
161                                 break;
162
163                         if (uniname <= last_period) {
164                                 if (uniname < last_period)
165                                         lossy = true;
166                                 uniname = last_period + 1;
167                         }
168                 }
169
170                 if (*uniname == (u16)'\0') {
171                         break;
172                 } else if (*uniname == (u16)' ') {
173                         lossy = true;
174                 } else if (*uniname == (u16)'.') {
175                         if (uniname < last_period)
176                                 lossy = true;
177                         else
178                                 i = 8;
179                 } else if (nls_wstrchr(bad_dos_chars, *uniname)) {
180                         lossy = true;
181                         *(dosname + i) = '_';
182                         i++;
183                 } else {
184                         len = convert_uni_to_ch(nls, buf, *uniname, &lossy);
185
186                         if (len > 1) {
187                                 if ((i >= 8) && ((i + len) > DOS_NAME_LENGTH))
188                                         break;
189
190                                 if ((i < 8) && ((i + len) > 8)) {
191                                         i = 8;
192                                         continue;
193                                 }
194
195                                 lower = 0xFF;
196
197                                 for (j = 0; j < len; j++, i++)
198                                         *(dosname + i) = *(buf + j);
199                         } else { /* len == 1 */
200                                 if ((*buf >= 'a') && (*buf <= 'z')) {
201                                         *(dosname + i) = *buf - ('a' - 'A');
202
203                                         if (i < 8)
204                                                 lower |= 0x08;
205                                         else
206                                                 lower |= 0x10;
207                                 } else if ((*buf >= 'A') && (*buf <= 'Z')) {
208                                         *(dosname + i) = *buf;
209
210                                         if (i < 8)
211                                                 upper |= 0x08;
212                                         else
213                                                 upper |= 0x10;
214                                 } else {
215                                         *(dosname + i) = *buf;
216                                 }
217                                 i++;
218                         }
219                 }
220
221                 uniname++;
222         }
223
224         if (*dosname == 0xE5)
225                 *dosname = 0x05;
226
227         if (*uniname != 0x0)
228                 lossy = true;
229
230         if (upper & lower)
231                 p_dosname->name_case = 0xFF;
232         else
233                 p_dosname->name_case = lower;
234
235         if (p_lossy)
236                 *p_lossy = lossy;
237 }
238
239 void nls_dosname_to_uniname(struct super_block *sb,
240                             struct uni_name_t *p_uniname,
241                             struct dos_name_t *p_dosname)
242 {
243         int i = 0, j, n = 0;
244         u8 buf[DOS_NAME_LENGTH + 2];
245         u8 *dosname = p_dosname->name;
246         u16 *uniname = p_uniname->name;
247         struct nls_table *nls = EXFAT_SB(sb)->nls_disk;
248
249         if (*dosname == 0x05) {
250                 *buf = 0xE5;
251                 i++;
252                 n++;
253         }
254
255         for (; i < 8; i++, n++) {
256                 if (*(dosname + i) == ' ')
257                         break;
258
259                 if ((*(dosname + i) >= 'A') && (*(dosname + i) <= 'Z') &&
260                     (p_dosname->name_case & 0x08))
261                         *(buf + n) = *(dosname + i) + ('a' - 'A');
262                 else
263                         *(buf + n) = *(dosname + i);
264         }
265         if (*(dosname + 8) != ' ') {
266                 *(buf + n) = '.';
267                 n++;
268         }
269
270         for (i = 8; i < DOS_NAME_LENGTH; i++, n++) {
271                 if (*(dosname + i) == ' ')
272                         break;
273
274                 if ((*(dosname + i) >= 'A') && (*(dosname + i) <= 'Z') &&
275                     (p_dosname->name_case & 0x10))
276                         *(buf + n) = *(dosname + i) + ('a' - 'A');
277                 else
278                         *(buf + n) = *(dosname + i);
279         }
280         *(buf + n) = '\0';
281
282         i = 0;
283         j = 0;
284         while (j < (MAX_NAME_LENGTH - 1)) {
285                 if (*(buf + i) == '\0')
286                         break;
287
288                 i += convert_ch_to_uni(nls, uniname, (buf + i), NULL);
289
290                 uniname++;
291                 j++;
292         }
293
294         *uniname = (u16)'\0';
295 }
296
297 void nls_uniname_to_cstring(struct super_block *sb, u8 *p_cstring,
298                             struct uni_name_t *p_uniname)
299 {
300         int i, j, len;
301         u8 buf[MAX_CHARSET_SIZE];
302         u16 *uniname = p_uniname->name;
303         struct nls_table *nls = EXFAT_SB(sb)->nls_io;
304
305         if (!nls) {
306                 len = utf16s_to_utf8s(uniname, MAX_NAME_LENGTH,
307                                       UTF16_HOST_ENDIAN, p_cstring,
308                                       MAX_NAME_LENGTH);
309                 p_cstring[len] = 0;
310                 return;
311         }
312
313         i = 0;
314         while (i < (MAX_NAME_LENGTH - 1)) {
315                 if (*uniname == (u16)'\0')
316                         break;
317
318                 len = convert_uni_to_ch(nls, buf, *uniname, NULL);
319
320                 if (len > 1) {
321                         for (j = 0; j < len; j++)
322                                 *p_cstring++ = (char)*(buf + j);
323                 } else { /* len == 1 */
324                         *p_cstring++ = (char)*buf;
325                 }
326
327                 uniname++;
328                 i++;
329         }
330
331         *p_cstring = '\0';
332 }
333
334 void nls_cstring_to_uniname(struct super_block *sb,
335                             struct uni_name_t *p_uniname, u8 *p_cstring,
336                             bool *p_lossy)
337 {
338         int i, j;
339         bool lossy = false;
340         u8 *end_of_name;
341         u8 upname[MAX_NAME_LENGTH * 2];
342         u16 *uniname = p_uniname->name;
343         struct nls_table *nls = EXFAT_SB(sb)->nls_io;
344
345         /* strip all trailing spaces */
346         end_of_name = p_cstring + strlen(p_cstring);
347
348         while (*(--end_of_name) == ' ') {
349                 if (end_of_name < p_cstring)
350                         break;
351         }
352         *(++end_of_name) = '\0';
353
354         if (strcmp(p_cstring, ".") && strcmp(p_cstring, "..")) {
355                 /* strip all trailing periods */
356                 while (*(--end_of_name) == '.') {
357                         if (end_of_name < p_cstring)
358                                 break;
359                 }
360                 *(++end_of_name) = '\0';
361         }
362
363         if (*p_cstring == '\0')
364                 lossy = true;
365
366         if (!nls) {
367                 i = utf8s_to_utf16s(p_cstring, MAX_NAME_LENGTH,
368                                     UTF16_HOST_ENDIAN, uniname,
369                                     MAX_NAME_LENGTH);
370                 for (j = 0; j < i; j++)
371                         SET16_A(upname + j * 2, nls_upper(sb, uniname[j]));
372                 uniname[i] = '\0';
373         } else {
374                 i = 0;
375                 j = 0;
376                 while (j < (MAX_NAME_LENGTH - 1)) {
377                         if (*(p_cstring + i) == '\0')
378                                 break;
379
380                         i += convert_ch_to_uni(nls, uniname,
381                                                (u8 *)(p_cstring + i), &lossy);
382
383                         if ((*uniname < 0x0020) ||
384                             nls_wstrchr(bad_uni_chars, *uniname))
385                                 lossy = true;
386
387                         SET16_A(upname + j * 2, nls_upper(sb, *uniname));
388
389                         uniname++;
390                         j++;
391                 }
392
393                 if (*(p_cstring + i) != '\0')
394                         lossy = true;
395                 *uniname = (u16)'\0';
396         }
397
398         p_uniname->name_len = j;
399         p_uniname->name_hash = calc_checksum_2byte(upname, j << 1, 0,
400                                                    CS_DEFAULT);
401
402         if (p_lossy)
403                 *p_lossy = lossy;
404 }