diff options
Diffstat (limited to 'vfat.c')
-rw-r--r-- | vfat.c | 831 |
1 files changed, 831 insertions, 0 deletions
@@ -0,0 +1,831 @@ +/* Copyright 1995 David C. Niemi + * Copyright 1996-2003,2005,2007-2009 Alain Knaff. + * This file is part of mtools. + * + * Mtools is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mtools is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Mtools. If not, see <http://www.gnu.org/licenses/>. + * + * vfat.c + * + * Miscellaneous VFAT-related functions + */ + +#include "sysincludes.h" +#include "msdos.h" +#include "mtools.h" +#include "vfat.h" +#include "file.h" +#include "dirCache.h" +#include "dirCacheP.h" +#include "file_name.h" + +/* #define DEBUG */ + +const char *short_illegals=";+=[]',\"*\\<>/?:|"; +const char *long_illegals = "\"*\\<>/?:|\005"; + +/* Automatically derive a new name */ +static void autorename(char *name, + char tilda, char dot, const char *illegals, + int limit, int bump) +{ + int tildapos, dotpos; + unsigned int seqnum=0, maxseq=0; + char tmp; + char *p; + +#ifdef DEBUG + printf("In autorename for name=%s.\n", name); +#endif + tildapos = -1; + + for(p=name; *p ; p++) + if (strchr(illegals, *p)) { + *p = '_'; + bump = 0; + } + + for(dotpos=0; + name[dotpos] && dotpos < limit && name[dotpos] != dot ; + dotpos++) { + if(name[dotpos] == tilda) { + tildapos = dotpos; + seqnum = 0; + maxseq = 1; + } else if (name[dotpos] >= '0' && name[dotpos] <= '9') { + seqnum = seqnum * 10 + name[dotpos] - '0'; + maxseq = maxseq * 10; + } else + tildapos = -1; /* sequence number interrupted */ + } + if(tildapos == -1) { + /* no sequence number yet */ + if(dotpos > limit - 2) { + tildapos = limit - 2; + dotpos = limit; + } else { + tildapos = dotpos; + dotpos += 2; + } + seqnum = 1; + } else { + if(bump) + seqnum++; + if(seqnum > 999999) { + seqnum = 1; + tildapos = dotpos - 2; + /* this matches Win95's behavior, and also guarantees + * us that the sequence numbers never get shorter */ + } + if (seqnum == maxseq) { + if(dotpos >= limit) + tildapos--; + else + dotpos++; + } + } + + tmp = name[dotpos]; + if((bump && seqnum == 1) || seqnum > 1 || mtools_numeric_tail) + sprintf(name+tildapos,"%c%d",tilda, seqnum); + if(dot) + name[dotpos]=tmp; + /* replace the character if it wasn't a space */ +#ifdef DEBUG + printf("Out autorename for name=%s.\n", name); +#endif +} + + +void autorename_short(dos_name_t *name, int bump) +{ + autorename(name->base, '~', ' ', short_illegals, 8, bump); +} + +void autorename_long(char *name, int bump) +{ + autorename(name, '-', '\0', long_illegals, 255, bump); +} + + +static __inline__ int unicode_read(struct unicode_char *in, + wchar_t *out, int num) +{ + wchar_t *end_out = out+num; + + while(out < end_out) { +#ifdef HAVE_WCHAR_H + *out = in->lchar | ((in->uchar) << 8); +#else + if (in->uchar) + *out = '_'; + else + *out = in->lchar; +#endif + ++out; + ++in; + } + return num; +} + + +void clear_vfat(struct vfat_state *v) +{ + v->subentries = 0; + v->status = 0; + v->present = 0; +} + + +/* sum_shortname + * + * Calculate the checksum that results from the short name in *dir. + * + * The sum is formed by circularly right-shifting the previous sum + * and adding in each character, from left to right, padding both + * the name and extension to maximum length with spaces and skipping + * the "." (hence always summing exactly 11 characters). + * + * This exact algorithm is required in order to remain compatible + * with Microsoft Windows-95 and Microsoft Windows NT 3.5. + * Thanks to Jeffrey Richter of Microsoft Systems Journal for + * pointing me to the correct algorithm. + * + * David C. Niemi (niemi@tuxers.net) 95.01.19 + */ +static __inline__ unsigned char sum_shortname(const dos_name_t *dn) +{ + unsigned char sum; + const char *name=dn->base; + const char *end = name+11; + + for (sum=0; name<end; ++name) + sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1) + + *name; + return(sum); +} + +/* check_vfat + * + * Inspect a directory and any associated VSEs. + * Return 1 if the VSEs comprise a valid long file name, + * 0 if not. + */ +static __inline__ void check_vfat(struct vfat_state *v, struct directory *dir) +{ + dos_name_t dn;; + + if (! v->subentries) { +#ifdef DEBUG + fprintf(stderr, "check_vfat: no VSEs.\n"); +#endif + return; + } + + memcpy(dn.base, (char *)dir->name, 8); + memcpy(dn.ext, (char *)dir->ext, 3); + + if (v->sum != sum_shortname(&dn)) + return; + + if( (v->status & ((1<<v->subentries) - 1)) != (1<<v->subentries) - 1) + return; /* missing entries */ + + /* zero out byte following last entry, for good measure */ + v->name[VSE_NAMELEN * v->subentries] = 0; + v->present = 1; +} + + +int clear_vses(Stream_t *Dir, int entrySlot, size_t last) +{ + direntry_t entry; + dirCache_t *cache; + int error; + + entry.Dir = Dir; + entry.entry = entrySlot; + + /*maximize(last, entry.entry + MAX_VFAT_SUBENTRIES);*/ + cache = allocDirCache(Dir, last); + if(!cache) { + fprintf(stderr, "Out of memory error in clear_vses\n"); + exit(1); + } + addFreeEntry(cache, entry.entry, last); + for (; entry.entry < (signed int) last; ++entry.entry) { +#ifdef DEBUG + fprintf(stderr,"Clearing entry %d.\n", entry.entry); +#endif + dir_read(&entry, &error); + if(error) + return error; + if(!entry.dir.name[0] || entry.dir.name[0] == DELMARK) + break; + entry.dir.name[0] = DELMARK; + if (entry.dir.attr == 0xf) + entry.dir.attr = '\0'; + low_level_dir_write(&entry); + } + return 0; +} + +int write_vfat(Stream_t *Dir, dos_name_t *shortname, char *longname, int start, + direntry_t *mainEntry) +{ + struct vfat_subentry *vse; + int vse_id, num_vses; + wchar_t *c; + direntry_t entry; + dirCache_t *cache; + wchar_t unixyName[13]; + doscp_t *cp = GET_DOSCONVERT(Dir); + + wchar_t wlongname[MAX_VNAMELEN+1]; + int wlen; + + if(longname) { +#ifdef DEBUG + printf("Entering write_vfat with longname=\"%s\", start=%d.\n", + longname,start); +#endif + entry.Dir = Dir; + vse = (struct vfat_subentry *) &entry.dir; + /* Fill in invariant part of vse */ + vse->attribute = 0x0f; + vse->hash1 = vse->sector_l = vse->sector_u = 0; + vse->sum = sum_shortname(shortname); +#ifdef DEBUG + printf("Wrote checksum=%d for shortname %s.%s\n", + vse->sum,shortname->base,shortname->ext); +#endif + + wlen = native_to_wchar(longname, wlongname, MAX_VNAMELEN+1, + 0, 0); + num_vses = (wlen + VSE_NAMELEN - 1)/VSE_NAMELEN; + for (vse_id = num_vses; vse_id; --vse_id) { + int end = 0; + + c = wlongname + (vse_id - 1) * VSE_NAMELEN; + + c += unicode_write(c, vse->text1, VSE1SIZE, &end); + c += unicode_write(c, vse->text2, VSE2SIZE, &end); + c += unicode_write(c, vse->text3, VSE3SIZE, &end); + + vse->id = (vse_id == num_vses) ? (vse_id | VSE_LAST) : vse_id; +#ifdef DEBUG + printf("Writing longname=(%s), VSE %d (%13s) at %d, end = %d.\n", + longname, vse_id, longname + (vse_id-1) * VSE_NAMELEN, + start + num_vses - vse_id, start + num_vses); +#endif + + entry.entry = start + num_vses - vse_id; + low_level_dir_write(&entry); + } + } else { + num_vses = 0; + wlongname[0]='\0'; + } + cache = allocDirCache(Dir, start + num_vses + 1); + if(!cache) { + fprintf(stderr, "Out of memory error\n"); + exit(1); + } + unix_name(cp, shortname->base, shortname->ext, 0, unixyName); + addUsedEntry(cache, start, start + num_vses + 1, wlongname, unixyName, + &mainEntry->dir); + low_level_dir_write(mainEntry); + return start + num_vses; +} + +void dir_write(direntry_t *entry) +{ + dirCacheEntry_t *dce; + dirCache_t *cache; + + if(entry->entry == -3) { + fprintf(stderr, "Attempt to write root directory pointer\n"); + exit(1); + } + + cache = allocDirCache(entry->Dir, entry->entry + 1); + if(!cache) { + fprintf(stderr, "Out of memory error in dir_write\n"); + exit(1); + } + dce = cache->entries[entry->entry]; + if(dce) { + if(entry->dir.name[0] == DELMARK) { + addFreeEntry(cache, dce->beginSlot, dce->endSlot); + } else { + dce->dir = entry->dir; + } + } + low_level_dir_write(entry); +} + + +/* + * The following function translates a series of vfat_subentries into + * data suitable for a dircache entry + */ +static __inline__ void parse_vses(direntry_t *entry, + struct vfat_state *v) +{ + struct vfat_subentry *vse; + unsigned char id, last_flag; + wchar_t *c; + + vse = (struct vfat_subentry *) &entry->dir; + + id = vse->id & VSE_MASK; + last_flag = (vse->id & VSE_LAST); + if (id > MAX_VFAT_SUBENTRIES) { + fprintf(stderr, "parse_vses: invalid VSE ID %d at %d.\n", + id, entry->entry); + return; + } + +/* 950819: This code enforced finding the VSEs in order. Well, Win95 + * likes to write them in *reverse* order for some bizarre reason! So + * we pretty much have to tolerate them coming in any possible order. + * So skip this check, we'll do without it (What does this do, Alain?). + * + * 950820: Totally rearranged code to tolerate any order but to warn if + * they are not in reverse order like Win95 uses. + * + * 950909: Tolerate any order. We recognize new chains by mismatching + * checksums. In the event that the checksums match, new entries silently + * overwrite old entries of the same id. This should accept all valid + * entries, but may fail to reject invalid entries in some rare cases. + */ + + /* bad checksum, begin new chain */ + if(v->sum != vse->sum) { + clear_vfat(v); + v->sum = vse->sum; + } + +#ifdef DEBUG + if(v->status & (1 << (id-1))) + fprintf(stderr, + "parse_vses: duplicate VSE %d\n", vse->id); +#endif + + v->status |= 1 << (id-1); + if(last_flag) + v->subentries = id; + +#ifdef DEBUG + if (id > v->subentries) + /* simple test to detect entries preceding + * the "last" entry (really the first) */ + fprintf(stderr, + "parse_vses: new VSE %d sans LAST flag\n", + vse->id); +#endif + + c = &(v->name[VSE_NAMELEN * (id-1)]); + c += unicode_read(vse->text1, c, VSE1SIZE); + c += unicode_read(vse->text2, c, VSE2SIZE); + c += unicode_read(vse->text3, c, VSE3SIZE); +#ifdef DEBUG + printf("Read VSE %d at %d, subentries=%d, = (%13ls).\n", + id,entry->entry,v->subentries,&(v->name[VSE_NAMELEN * (id-1)])); +#endif + if (last_flag) + *c = '\0'; /* Null terminate long name */ +} + +/** + * Read one complete entry from directory (main name plus any VSEs + * belonging to it) + */ +static dirCacheEntry_t *vfat_lookup_loop_common(doscp_t *cp, + direntry_t *direntry, + dirCache_t *cache, + int lookForFreeSpace, + int *io_error) +{ + wchar_t newfile[13]; + int initpos = direntry->entry + 1; + struct vfat_state vfat; + wchar_t *longname; + int error; + int endmarkSeen = 0; + + /* not yet cached */ + *io_error = 0; + clear_vfat(&vfat); + while(1) { + ++direntry->entry; + if(!dir_read(direntry, &error)){ + if(error) { + *io_error = error; + return NULL; + } + addFreeEndEntry(cache, initpos, direntry->entry, + endmarkSeen); + return addEndEntry(cache, direntry->entry); + } + + if (endmarkSeen || direntry->dir.name[0] == ENDMARK){ + /* the end of the directory */ + if(lookForFreeSpace) { + endmarkSeen = 1; + continue; + } + return addEndEntry(cache, direntry->entry); + } + if(direntry->dir.name[0] != DELMARK && + direntry->dir.attr == 0x0f) + parse_vses(direntry, &vfat); + else + /* the main entry */ + break; + } + + /* If we get here, it's a short name FAT entry, maybe erased. + * thus we should make sure that the vfat structure will be + * cleared before the next loop run */ + + /* deleted file */ + if (direntry->dir.name[0] == DELMARK) { + return addFreeEntry(cache, initpos, + direntry->entry + 1); + } + + check_vfat(&vfat, &direntry->dir); + if(!vfat.present) + vfat.subentries = 0; + + /* mark space between last entry and this one as free */ + addFreeEntry(cache, initpos, + direntry->entry - vfat.subentries); + + if (direntry->dir.attr & 0x8){ + /* Read entry as a label */ + wchar_t *ptr = newfile; + ptr += dos_to_wchar(cp, direntry->dir.name, ptr, 8); + ptr += dos_to_wchar(cp, direntry->dir.ext, ptr, 3); + *ptr = '\0'; + } else + unix_name(cp, + direntry->dir.name, + direntry->dir.ext, + direntry->dir.Case, + newfile); + + if(vfat.present) + longname = vfat.name; + else + longname = 0; + + return addUsedEntry(cache, direntry->entry - vfat.subentries, + direntry->entry + 1, longname, + newfile, &direntry->dir); +} + +static __inline__ dirCacheEntry_t *vfat_lookup_loop_for_read(doscp_t *cp, + direntry_t *direntry, + dirCache_t *cache, + int *io_error) +{ + int initpos = direntry->entry + 1; + dirCacheEntry_t *dce; + + *io_error = 0; + dce = cache->entries[initpos]; + if(dce) { + direntry->entry = dce->endSlot - 1; + return dce; + } else { + return vfat_lookup_loop_common(cp, + direntry, cache, 0, io_error); + } +} + + +typedef enum result_t { + RES_NOMATCH, + RES_MATCH, + RES_END, + RES_ERROR +} result_t; + + +/* + * 0 does not match + * 1 matches + * 2 end + */ +static result_t checkNameForMatch(struct direntry_t *direntry, + dirCacheEntry_t *dce, + const wchar_t *filename, + int length, + int flags) +{ + switch(dce->type) { + case DCET_FREE: + return RES_NOMATCH; + case DCET_END: + return RES_END; + case DCET_USED: + break; + default: + fprintf(stderr, "Unexpected entry type %d\n", + dce->type); + return RES_ERROR; + } + + direntry->dir = dce->dir; + + /* make sure the entry is of an accepted type */ + if((direntry->dir.attr & 0x8) && !(flags & ACCEPT_LABEL)) + return RES_NOMATCH; + + + /*---------- multiple files ----------*/ + if(!((flags & MATCH_ANY) || + (dce->longName && + match(dce->longName, filename, direntry->name, 0, length)) || + match(dce->shortName, filename, direntry->name, 1, length))) { + + return RES_NOMATCH; + } + + /* entry of non-requested type, has to come after name + * checking because of clash handling */ + if(IS_DIR(direntry) && !(flags & ACCEPT_DIR)) { + if(!(flags & (ACCEPT_LABEL|MATCH_ANY|NO_MSG))) { + char tmp[4*13+1]; + wchar_to_native(dce->shortName,tmp,13); + fprintf(stderr, "Skipping \"%s\", is a directory\n", + tmp); + } + return RES_NOMATCH; + } + + if(!(direntry->dir.attr & (ATTR_LABEL | ATTR_DIR)) && + !(flags & ACCEPT_PLAIN)) { + if(!(flags & (ACCEPT_LABEL|MATCH_ANY|NO_MSG))) { + char tmp[4*13+1]; + wchar_to_native(dce->shortName,tmp,13); + fprintf(stderr, + "Skipping \"%s\", is not a directory\n", + tmp); + } + return RES_NOMATCH; + } + + return RES_MATCH; +} + + +/* + * vfat_lookup looks for filenames in directory dir. + * if a name if found, it is returned in outname + * if applicable, the file is opened and its stream is returned in File + */ + +int vfat_lookup(direntry_t *direntry, const char *filename, int length, + int flags, char *shortname, char *longname) +{ + dirCacheEntry_t *dce; + result_t result; + dirCache_t *cache; + int io_error; + wchar_t wfilename[MAX_VNAMELEN+1]; + doscp_t *cp = GET_DOSCONVERT(direntry->Dir); + + if(length == -1 && filename) + length = strlen(filename); + + if(filename != NULL) + length = native_to_wchar(filename, wfilename, MAX_VNAMELEN, + filename+length, 0); + else + length = 0; + + if (direntry->entry == -2) + return -1; + + cache = allocDirCache(direntry->Dir, direntry->entry+1); + if(!cache) { + fprintf(stderr, "Out of memory error in vfat_lookup [0]\n"); + exit(1); + } + + do { + dce = vfat_lookup_loop_for_read(cp, direntry, cache, &io_error); + if(!dce) { + if (io_error) + return -2; + fprintf(stderr, "Out of memory error in vfat_lookup\n"); + exit(1); + } + result = checkNameForMatch(direntry, dce, + wfilename, + length, flags); + } while(result == RES_NOMATCH); + + if(result == RES_MATCH){ + if(longname){ + if(dce->longName) + wchar_to_native(dce->longName, + longname, MAX_VNAMELEN); + else + *longname ='\0'; + } + if(shortname) + wchar_to_native(dce->shortName, shortname, 12); + direntry->beginSlot = dce->beginSlot; + direntry->endSlot = dce->endSlot-1; + return 0; /* file found */ + } else { + direntry->entry = -2; + return -1; /* no file found */ + } +} + +static __inline__ dirCacheEntry_t *vfat_lookup_loop_for_insert(doscp_t *cp, + direntry_t *direntry, + int initpos, + dirCache_t *cache) +{ + dirCacheEntry_t *dce; + int io_error; + + dce = cache->entries[initpos]; + if(dce && dce->type != DCET_END) { + return dce; + } else { + direntry->entry = initpos - 1; + dce = vfat_lookup_loop_common(cp, + direntry, cache, 1, &io_error); + if(!dce) { + if (io_error) { + return NULL; + } + fprintf(stderr, + "Out of memory error in vfat_lookup_loop\n"); + exit(1); + } + return cache->entries[initpos]; + } +} + +static void accountFreeSlots(struct scan_state *ssp, dirCacheEntry_t *dce) +{ + if(ssp->got_slots) + return; + + if(ssp->free_end != dce->beginSlot) { + ssp->free_start = dce->beginSlot; + } + ssp->free_end = dce->endSlot; + + if(ssp->free_end - ssp->free_start >= ssp->size_needed) { + ssp->got_slots = 1; + ssp->slot = ssp->free_start + ssp->size_needed - 1; + } +} + +static void clear_scan(wchar_t *longname, int use_longname, + struct scan_state *s) +{ + s->shortmatch = s->longmatch = s->slot = -1; + s->free_end = s->got_slots = s->free_start = 0; + + if (use_longname & 1) + s->size_needed = 1 + + (wcslen(longname) + VSE_NAMELEN - 1)/VSE_NAMELEN; + else + s->size_needed = 1; +} + +/* lookup_for_insert replaces the old scandir function. It directly + * calls into vfat_lookup_loop, thus eliminating the overhead of the + * normal vfat_lookup + */ +int lookupForInsert(Stream_t *Dir, + struct direntry_t *direntry, + dos_name_t *dosname, + char *longname, + struct scan_state *ssp, + int ignore_entry, + int source_entry, + int pessimisticShortRename, + int use_longname) +{ + direntry_t entry; + int ignore_match; + dirCacheEntry_t *dce; + dirCache_t *cache; + int pos; /* position _before_ the next answered entry */ + wchar_t shortName[13]; + wchar_t wlongname[MAX_VNAMELEN+1]; + doscp_t *cp = GET_DOSCONVERT(Dir); + + native_to_wchar(longname, wlongname, MAX_VNAMELEN+1, 0, 0); + clear_scan(wlongname, use_longname, ssp); + + ignore_match = (ignore_entry == -2 ); + + initializeDirentry(&entry, Dir); + ssp->match_free = 0; + + /* hash bitmap of already encountered names. Speeds up batch appends + * to huge directories, because in the best case, we only need to scan + * the new entries rather than the whole directory */ + cache = allocDirCache(Dir, 1); + if(!cache) { + fprintf(stderr, "Out of memory error in lookupForInsert\n"); + exit(1); + } + + if(!ignore_match) + unix_name(cp, dosname->base, dosname->ext, 0, shortName); + + pos = cache->nrHashed; + if(source_entry >= 0 || + (pos && isHashed(cache, wlongname))) { + pos = 0; + } else if(pos && !ignore_match && isHashed(cache, shortName)) { + if(pessimisticShortRename) { + ssp->shortmatch = -2; + return 1; + } + pos = 0; + } else if(growDirCache(cache, pos) < 0) { + fprintf(stderr, "Out of memory error in vfat_looup [0]\n"); + exit(1); + } + do { + dce = vfat_lookup_loop_for_insert(cp, &entry, pos, cache); + switch(dce->type) { + case DCET_FREE: + accountFreeSlots(ssp, dce); + break; + case DCET_USED: + if(!(dce->dir.attr & 0x8) && + (signed int)dce->endSlot-1 == source_entry) + accountFreeSlots(ssp, dce); + + /* labels never match, neither does the + * ignored entry */ + if( (dce->dir.attr & 0x8) || + ((signed int)dce->endSlot-1==ignore_entry)) + break; + + /* check long name */ + if((dce->longName && + !wcscasecmp(dce->longName, wlongname)) || + (dce->shortName && + !wcscasecmp(dce->shortName, wlongname))) { + ssp->longmatch = dce->endSlot - 1; + /* long match is a reason for + * immediate stop */ + direntry->beginSlot = dce->beginSlot; + direntry->endSlot = dce->endSlot-1; + return 1; + } + + /* Long name or not, always check for + * short name match */ + if (!ignore_match && + !wcscasecmp(shortName, dce->shortName)) + ssp->shortmatch = dce->endSlot - 1; + break; + case DCET_END: + break; + } + pos = dce->endSlot; + } while(dce->type != DCET_END); + if (ssp->shortmatch > -1) + return 1; + ssp->max_entry = dce->beginSlot; + if (ssp->got_slots) + return 6; /* Success */ + + /* Need more room. Can we grow the directory? */ + if(!isRootDir(Dir)) + return 5; /* OK, try to grow the directory */ + + fprintf(stderr, "No directory slots\n"); + return -1; +} + + + +/* End vfat.c */ |