dosfstools  4.2
About: dosfstools are utilities to create, check and label (MS-DOS) FAT filesystems.
  Fossies Dox: dosfstools-4.2.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

lfn.c
Go to the documentation of this file.
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)
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;
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;
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",
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:
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  }
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 
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 }
void die(const char *msg,...)
Definition: common.c:53
int xasprintf(char **strp, const char *fmt,...)
Definition: common.c:141
void * alloc(int size)
Definition: common.c:81
void * qalloc(void **root, int size)
Definition: common.c:91
int get_choice(int noninteractive_result, const char *noninteractive_msg, int choices,...)
Definition: common.c:157
void * mem_queue
Definition: fatlabel.c:52
char * file_name(unsigned char *fixed)
Definition: file.c:63
#define VFAT_LN_ATTR
Definition: fsck.fat.h:39
void fs_write(off_t pos, int size, void *data)
Definition: io.c:114
#define CNV_PARTS_SO_FAR()
Definition: lfn.c:79
#define UNICODE_CONVERTABLE(cl, ch)
Definition: lfn.c:72
void lfn_add_slot(DIR_ENT *de, off_t dir_offset)
Definition: lfn.c:200
static unsigned char fat_uni2esc[64]
Definition: lfn.c:60
int lfn_slot
Definition: lfn.c:56
static char * cnv_unicode(const unsigned char *uni, int maxlen, int use_q)
Definition: lfn.c:102
static size_t wctombs(char *dest, wchar_t x)
Definition: lfn.c:90
int lfn_parts
Definition: lfn.c:58
#define CHARS_PER_LFN
Definition: lfn.c:51
static size_t mbslen(wchar_t x)
Definition: lfn.c:84
#define LFN_ID_SLOTMASK
Definition: lfn.c:49
off_t * lfn_offsets
Definition: lfn.c:57
unsigned char lfn_checksum
Definition: lfn.c:55
static void clear_lfn_slots(int start, int end)
Definition: lfn.c:158
void lfn_check_orphaned(void)
Definition: lfn.c:512
void lfn_fix_checksum(off_t from, off_t to, const char *short_name)
Definition: lfn.c:175
static void copy_lfn_part(unsigned char *dst, LFN_ENT *lfn)
Definition: lfn.c:143
static char * cnv_this_part(LFN_ENT *lfn)
Definition: lfn.c:151
void lfn_reset(void)
Definition: lfn.c:187
char * lfn_get(DIR_ENT *de, off_t *lfn_offset)
Definition: lfn.c:399
#define LFN_ID_START
Definition: lfn.c:48
#define BYTES_TO_WCHAR(cl, ch)
Definition: lfn.c:83
#define UNTIL_0
Definition: lfn.c:75
unsigned char * lfn_unicode
Definition: lfn.c:54
#define DELETED_FLAG
Definition: msdos_fs.h:41
#define MSDOS_NAME
Definition: msdos_fs.h:44
uint8_t lcase
Definition: fsck.fat.h:132
uint8_t attr
Definition: fsck.fat.h:131
uint8_t name[11]
Definition: fsck.fat.h:130
Definition: lfn.c:37
uint8_t id
Definition: lfn.c:38
uint8_t name5_10[12]
Definition: lfn.c:43
uint8_t reserved
Definition: lfn.c:41
uint8_t attr
Definition: lfn.c:40
uint16_t start
Definition: lfn.c:44
uint8_t alias_checksum
Definition: lfn.c:42
uint8_t name11_12[4]
Definition: lfn.c:45
uint8_t name0_4[10]
Definition: lfn.c:39