"Fossies" - the Fresh Open Source Software Archive 
Member "dosfstools-4.2/src/lfn.c" (31 Jan 2021, 15507 Bytes) of package /linux/misc/dosfstools-4.2.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "lfn.c" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
4.1_vs_4.2.
1 /* lfn.c - Functions for handling VFAT long filenames
2
3 Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
4 Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
5 Copyright (C) 2015 Andreas Bombe <aeb@debian.org>
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 The complete text of the GNU General Public License
21 can be found in /usr/share/common-licenses/GPL-3 file.
22 */
23
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <limits.h>
29 #include <time.h>
30
31 #include "common.h"
32 #include "io.h"
33 #include "fsck.fat.h"
34 #include "lfn.h"
35 #include "file.h"
36
37 typedef struct {
38 uint8_t id; /* sequence number for slot */
39 uint8_t name0_4[10]; /* first 5 characters in name */
40 uint8_t attr; /* attribute byte */
41 uint8_t reserved; /* always 0 */
42 uint8_t alias_checksum; /* checksum for 8.3 alias */
43 uint8_t name5_10[12]; /* 6 more characters in name */
44 uint16_t start; /* starting cluster number, 0 in long slots */
45 uint8_t name11_12[4]; /* last 2 characters in name */
46 } LFN_ENT;
47
48 #define LFN_ID_START 0x40
49 #define LFN_ID_SLOTMASK 0x1f
50
51 #define CHARS_PER_LFN 13
52
53 /* These modul-global vars represent the state of the LFN parser */
54 unsigned char *lfn_unicode = NULL;
55 unsigned char lfn_checksum;
56 int lfn_slot = -1;
57 off_t *lfn_offsets = NULL;
58 int lfn_parts = 0;
59
60 static unsigned char fat_uni2esc[64] = {
61 '0', '1', '2', '3', '4', '5', '6', '7',
62 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
63 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
64 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
65 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
66 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
67 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
68 'u', 'v', 'w', 'x', 'y', 'z', '+', '-'
69 };
70
71 /* This defines which unicode chars are directly convertable to ISO-8859-1 */
72 #define UNICODE_CONVERTABLE(cl,ch) (ch == 0 && (cl < 0x80 || cl >= 0xa0))
73
74 /* for maxlen param */
75 #define UNTIL_0 INT_MAX
76
77 /* Convert name parts collected so far (from previous slots) from unicode to
78 * ASCII */
79 #define CNV_PARTS_SO_FAR() \
80 (cnv_unicode( lfn_unicode+(lfn_slot*CHARS_PER_LFN*2), \
81 lfn_parts*CHARS_PER_LFN, 0 ))
82
83 #define BYTES_TO_WCHAR(cl,ch) ((wchar_t)((unsigned)(cl) + ((unsigned)(ch) << 8)))
84 static size_t mbslen(wchar_t x)
85 {
86 wchar_t wstr[] = { x, 0 };
87 return wcstombs(NULL, wstr, 0);
88 }
89
90 static size_t wctombs(char *dest, wchar_t x)
91 {
92 wchar_t wstr[] = { x, 0 };
93 size_t size = wcstombs(NULL, wstr, 0);
94 if (size != (size_t) - 1)
95 size = wcstombs(dest, wstr, size + 1);
96 return size;
97 }
98
99 /* This function converts an unicode string to a normal ASCII string, assuming
100 * ISO-8859-1 charset. Characters not in 8859-1 are converted to the same
101 * escape notation as used by the kernel, i.e. the uuencode-like ":xxx" */
102 static char *cnv_unicode(const unsigned char *uni, int maxlen, int use_q)
103 {
104 const unsigned char *up;
105 unsigned char *out, *cp;
106 int len, val;
107 size_t x;
108
109 for (len = 0, up = uni; (up - uni) / 2 < maxlen && (up[0] || up[1]);
110 up += 2) {
111 if ((x = mbslen(BYTES_TO_WCHAR(up[0], up[1]))) != (size_t) - 1)
112 len += x;
113 else if (UNICODE_CONVERTABLE(up[0], up[1]))
114 ++len;
115 else
116 len += 4;
117 }
118 cp = out = use_q ? qalloc(&mem_queue, len + 1) : alloc(len + 1);
119
120 for (up = uni; (up - uni) / 2 < maxlen && (up[0] || up[1]); up += 2) {
121 if ((x =
122 wctombs((char *)cp, BYTES_TO_WCHAR(up[0], up[1]))) != (size_t) - 1)
123 cp += x;
124 else if (UNICODE_CONVERTABLE(up[0], up[1]))
125 *cp++ = up[0];
126 else {
127 /* here the same escape notation is used as in the Linux kernel */
128 *cp++ = ':';
129 val = (up[1] << 8) + up[0];
130 cp[2] = fat_uni2esc[val & 0x3f];
131 val >>= 6;
132 cp[1] = fat_uni2esc[val & 0x3f];
133 val >>= 6;
134 cp[0] = fat_uni2esc[val & 0x3f];
135 cp += 3;
136 }
137 }
138 *cp = 0;
139
140 return (char *)out;
141 }
142
143 static void copy_lfn_part(unsigned char *dst, LFN_ENT * lfn)
144 {
145 memcpy(dst, lfn->name0_4, 10);
146 memcpy(dst + 10, lfn->name5_10, 12);
147 memcpy(dst + 22, lfn->name11_12, 4);
148 }
149
150 /* Convert name part in 'lfn' from unicode to ASCII */
151 static inline char *cnv_this_part(LFN_ENT *lfn)
152 {
153 unsigned char part_uni[CHARS_PER_LFN * 2];
154 copy_lfn_part(part_uni, lfn);
155 return cnv_unicode(part_uni, CHARS_PER_LFN, 0);
156 }
157
158 static void clear_lfn_slots(int start, int end)
159 {
160 int i;
161 LFN_ENT empty;
162
163 /* New dir entry is zeroed except first byte, which is set to 0xe5.
164 * This is to avoid that some FAT-reading OSes (not Linux! ;) stop reading
165 * a directory at the first zero entry...
166 */
167 memset(&empty, 0, sizeof(empty));
168 empty.id = DELETED_FLAG;
169
170 for (i = start; i <= end; ++i) {
171 fs_write(lfn_offsets[i], sizeof(LFN_ENT), &empty);
172 }
173 }
174
175 void lfn_fix_checksum(off_t from, off_t to, const char *short_name)
176 {
177 int i;
178 uint8_t sum;
179 for (sum = 0, i = 0; i < 11; i++)
180 sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + short_name[i];
181
182 for (; from < to; from += sizeof(LFN_ENT)) {
183 fs_write(from + offsetof(LFN_ENT, alias_checksum), sizeof(sum), &sum);
184 }
185 }
186
187 void lfn_reset(void)
188 {
189 if (lfn_unicode)
190 free(lfn_unicode);
191 lfn_unicode = NULL;
192 if (lfn_offsets)
193 free(lfn_offsets);
194 lfn_offsets = NULL;
195 lfn_slot = -1;
196 }
197
198 /* This function is only called with de->attr == VFAT_LN_ATTR. It stores part
199 * of the long name. */
200 void lfn_add_slot(DIR_ENT * de, off_t dir_offset)
201 {
202 LFN_ENT *lfn = (LFN_ENT *) de;
203 int slot = lfn->id & LFN_ID_SLOTMASK;
204 unsigned offset;
205
206 if (lfn_slot == 0)
207 lfn_check_orphaned();
208
209 if (de->attr != VFAT_LN_ATTR)
210 die("lfn_add_slot called with non-LFN directory entry");
211
212 if (lfn->id & LFN_ID_START && slot != 0) {
213 if (lfn_slot != -1) {
214 int can_clear = 0;
215 /* There is already a LFN "in progess", so it is an error that a
216 * new start entry is here. */
217 /* Causes: 1) if slot# == expected: start bit set mysteriously, 2)
218 * old LFN overwritten by new one */
219 /* Fixes: 1) delete previous LFN 2) if slot# == expected and
220 * checksum ok: clear start bit */
221 /* XXX: Should delay that until next LFN known (then can better
222 * display the name) */
223 printf("A new long file name starts within an old one.\n");
224 if (slot == lfn_slot && lfn->alias_checksum == lfn_checksum) {
225 char *part1 = cnv_this_part(lfn);
226 char *part2 = CNV_PARTS_SO_FAR();
227 printf(" It could be that the LFN start bit is wrong here\n"
228 " if \"%s\" seems to match \"%s\".\n", part1, part2);
229 free(part1);
230 free(part2);
231 can_clear = 1;
232 }
233 switch (get_choice(2, " Not auto-correcting this.",
234 can_clear ? 3 : 2,
235 1, "Delete previous LFN",
236 2, "Leave it as it is",
237 3, "Clear start bit and concatenate LFNs")) {
238 case 1:
239 clear_lfn_slots(0, lfn_parts - 1);
240 lfn_reset();
241 break;
242 case 2:
243 break;
244 case 3:
245 lfn->id &= ~LFN_ID_START;
246 fs_write(dir_offset + offsetof(LFN_ENT, id),
247 sizeof(lfn->id), &lfn->id);
248 break;
249 }
250 }
251 lfn_slot = slot;
252 lfn_checksum = lfn->alias_checksum;
253 lfn_unicode = alloc((lfn_slot * CHARS_PER_LFN + 1) * 2);
254 lfn_offsets = alloc(lfn_slot * sizeof(off_t));
255 lfn_parts = 0;
256 } else if (lfn_slot == -1 && slot != 0) {
257 /* No LFN in progress, but slot found; start bit missing */
258 /* Causes: 1) start bit got lost, 2) Previous slot with start bit got
259 * lost */
260 /* Fixes: 1) delete LFN, 2) set start bit */
261 char *part = cnv_this_part(lfn);
262 printf("Long filename fragment \"%s\" found outside a LFN "
263 "sequence.\n (Maybe the start bit is missing on the "
264 "last fragment)\n", part);
265 free(part);
266 switch (get_choice(2, " Not auto-correcting this.",
267 3,
268 1, "Delete fragment",
269 2, "Leave it as it is",
270 3, "Set start bit")) {
271 case 1:
272 if (!lfn_offsets)
273 lfn_offsets = alloc(sizeof(off_t));
274 lfn_offsets[0] = dir_offset;
275 clear_lfn_slots(0, 0);
276 lfn_reset();
277 return;
278 case 2:
279 lfn_reset();
280 return;
281 case 3:
282 lfn->id |= LFN_ID_START;
283 fs_write(dir_offset + offsetof(LFN_ENT, id),
284 sizeof(lfn->id), &lfn->id);
285 lfn_slot = slot;
286 lfn_checksum = lfn->alias_checksum;
287 lfn_unicode = alloc((lfn_slot * CHARS_PER_LFN + 1) * 2);
288 lfn_offsets = alloc(lfn_slot * sizeof(off_t));
289 lfn_parts = 0;
290 break;
291 }
292 } else if (slot != lfn_slot) {
293 /* wrong sequence number */
294 /* Causes: 1) seq-no destroyed */
295 /* Fixes: 1) delete LFN, 2) fix number (maybe only if following parts
296 * are ok?, maybe only if checksum is ok?) (Attention: space
297 * for name was allocated before!) */
298 int can_fix = 0;
299 printf("Unexpected long filename sequence number "
300 "(%d vs. expected %d).\n", slot, lfn_slot);
301 if (lfn->alias_checksum == lfn_checksum && lfn_slot > 0) {
302 char *part1 = cnv_this_part(lfn);
303 char *part2 = CNV_PARTS_SO_FAR();
304 printf(" It could be that just the number is wrong\n"
305 " if \"%s\" seems to match \"%s\".\n", part1, part2);
306 free(part1);
307 free(part2);
308 can_fix = 1;
309 }
310 switch (get_choice(2, " Not auto-correcting this.",
311 can_fix ? 3 : 2,
312 1, "Delete LFN",
313 2, "Leave it as it is (and ignore LFN so far)",
314 3, "Correct sequence number")) {
315 case 1:
316 if (!lfn_offsets) {
317 lfn_offsets = alloc(sizeof(off_t));
318 lfn_parts = 0;
319 }
320 lfn_offsets[lfn_parts++] = dir_offset;
321 clear_lfn_slots(0, lfn_parts - 1);
322 lfn_reset();
323 return;
324 case 2:
325 lfn_reset();
326 return;
327 case 3:
328 lfn->id = (lfn->id & ~LFN_ID_SLOTMASK) | lfn_slot;
329 fs_write(dir_offset + offsetof(LFN_ENT, id),
330 sizeof(lfn->id), &lfn->id);
331 break;
332 }
333 }
334
335 if (lfn->alias_checksum != lfn_checksum) {
336 /* checksum mismatch */
337 /* Causes: 1) checksum field here destroyed */
338 /* Fixes: 1) delete LFN, 2) fix checksum */
339 printf("Checksum in long filename part wrong "
340 "(%02x vs. expected %02x).\n",
341 lfn->alias_checksum, lfn_checksum);
342 switch (get_choice(2, " Not auto-correcting this.",
343 3,
344 1, "Delete LFN",
345 2, "Leave it as it is",
346 3, "Correct checksum")) {
347 case 1:
348 lfn_offsets[lfn_parts++] = dir_offset;
349 clear_lfn_slots(0, lfn_parts - 1);
350 lfn_reset();
351 return;
352 case 2:
353 break;
354 case 3:
355 lfn->alias_checksum = lfn_checksum;
356 fs_write(dir_offset + offsetof(LFN_ENT, alias_checksum),
357 sizeof(lfn->alias_checksum), &lfn->alias_checksum);
358 break;
359 }
360 }
361
362 if (lfn_slot != -1) {
363 lfn_slot--;
364 offset = lfn_slot * CHARS_PER_LFN * 2;
365 copy_lfn_part(lfn_unicode + offset, lfn);
366 if (lfn->id & LFN_ID_START)
367 lfn_unicode[offset + 26] = lfn_unicode[offset + 27] = 0;
368 lfn_offsets[lfn_parts++] = dir_offset;
369 }
370
371 if (lfn->reserved != 0) {
372 printf("Reserved field in VFAT long filename slot is not 0 "
373 "(but 0x%02x).\n", lfn->reserved);
374 if (get_choice(1, "Auto-setting to 0.",
375 2,
376 1, "Fix",
377 2, "Leave it") == 1) {
378 lfn->reserved = 0;
379 fs_write(dir_offset + offsetof(LFN_ENT, reserved),
380 sizeof(lfn->reserved), &lfn->reserved);
381 }
382 }
383 if (lfn->start != htole16(0)) {
384 printf("Start cluster field in VFAT long filename slot is not 0 "
385 "(but 0x%04x).\n", lfn->start);
386 if (get_choice(1, "Auto-setting to 0.",
387 2,
388 1, "Fix",
389 2, "Leave it") == 1) {
390 lfn->start = htole16(0);
391 fs_write(dir_offset + offsetof(LFN_ENT, start),
392 sizeof(lfn->start), &lfn->start);
393 }
394 }
395 }
396
397 /* This function is always called when de->attr != VFAT_LN_ATTR is found, to
398 * retrieve the previously constructed LFN. */
399 char *lfn_get(DIR_ENT * de, off_t * lfn_offset)
400 {
401 char *lfn;
402 uint8_t sum;
403 int i;
404
405 *lfn_offset = 0;
406 if (de->attr == VFAT_LN_ATTR)
407 die("lfn_get called with LFN directory entry");
408
409 #if 0
410 if (de->lcase)
411 printf("lcase=%02x\n", de->lcase);
412 #endif
413
414 if (lfn_slot == -1)
415 /* no long name for this file */
416 return NULL;
417
418 if (lfn_slot != 0) {
419 /* The long name isn't finished yet. */
420 /* Causes: 1) LFN slot overwritten by non-VFAT aware tool */
421 /* Fixes: 1) delete LFN 2) move overwriting entry to somewhere else
422 * and let user enter missing part of LFN (hard to do :-()
423 * 3) renumber entries and truncate name */
424 char *long_name = CNV_PARTS_SO_FAR();
425 char *short_name = file_name(de->name);
426 char *fix_num_string;
427 int choice;
428
429 printf("Unfinished long file name \"%s\".\n"
430 " (Start may have been overwritten by %s)\n",
431 long_name, short_name);
432 free(long_name);
433
434 xasprintf(&fix_num_string,
435 "Fix numbering (truncates long name and attaches "
436 "it to short name %s)", short_name);
437 choice = get_choice(2, " Not auto-correcting this.",
438 3,
439 1, "Delete LFN",
440 2, "Leave it as it is",
441 3, fix_num_string);
442 free(fix_num_string);
443
444 switch (choice) {
445 case 1:
446 clear_lfn_slots(0, lfn_parts - 1);
447 lfn_reset();
448 return NULL;
449 case 2:
450 lfn_reset();
451 return NULL;
452 case 3:
453 for (i = 0; i < lfn_parts; ++i) {
454 uint8_t id = (lfn_parts - i) | (i == 0 ? LFN_ID_START : 0);
455 fs_write(lfn_offsets[i] + offsetof(LFN_ENT, id),
456 sizeof(id), &id);
457 }
458 memmove(lfn_unicode, lfn_unicode + lfn_slot * CHARS_PER_LFN * 2,
459 lfn_parts * CHARS_PER_LFN * 2);
460 break;
461 }
462 }
463
464 for (sum = 0, i = 0; i < MSDOS_NAME; i++)
465 sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + de->name[i];
466 if (sum != lfn_checksum) {
467 /* checksum doesn't match, long name doesn't apply to this alias */
468 /* Causes: 1) alias renamed */
469 /* Fixes: 1) Fix checksum in LFN entries */
470 char *long_name = CNV_PARTS_SO_FAR();
471 char *short_name = file_name(de->name);
472 char *fix_check_string;
473 int choice;
474
475 printf("Wrong checksum for long file name \"%s\".\n"
476 " (Short name %s may have changed without updating the long name)\n",
477 long_name, short_name);
478 free(long_name);
479
480 xasprintf(&fix_check_string,
481 "Fix checksum (attaches to short name %s)", short_name);
482 choice = get_choice(9, " Not auto-correcting this.",
483 3,
484 1, "Delete LFN",
485 2, "Leave it as it is",
486 3, fix_check_string);
487 free(fix_check_string);
488
489 switch (choice) {
490 case 1:
491 clear_lfn_slots(0, lfn_parts - 1);
492 lfn_reset();
493 return NULL;
494 case 2:
495 lfn_reset();
496 return NULL;
497 case 3:
498 for (i = 0; i < lfn_parts; ++i) {
499 fs_write(lfn_offsets[i] + offsetof(LFN_ENT, alias_checksum),
500 sizeof(sum), &sum);
501 }
502 break;
503 }
504 }
505
506 *lfn_offset = lfn_offsets[0];
507 lfn = cnv_unicode(lfn_unicode, UNTIL_0, 1);
508 lfn_reset();
509 return (lfn);
510 }
511
512 void lfn_check_orphaned(void)
513 {
514 char *long_name;
515
516 if (lfn_slot == -1)
517 return;
518
519 long_name = CNV_PARTS_SO_FAR();
520 printf("Orphaned long file name part \"%s\"\n", long_name);
521 free(long_name);
522 if (get_choice(1, " Auto-deleting.",
523 2,
524 1, "Delete",
525 2, "Leave it") == 1) {
526 clear_lfn_slots(0, lfn_parts - 1);
527 }
528 lfn_reset();
529 }