"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 }