1 /* Copyright 1995 David C. Niemi
2 * Copyright 1996-2003,2005,2007-2009 Alain Knaff.
3 * This file is part of mtools.
5 * Mtools is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * Mtools is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with Mtools. If not, see <http://www.gnu.org/licenses/>.
20 * Miscellaneous VFAT-related functions
23 #include "sysincludes.h"
28 #include "dirCacheP.h"
29 #include "file_name.h"
40 struct vfat_subentry {
41 unsigned char id; /* 0x40 = last; & 0x1f = VSE ID */
42 unsigned char text1[VSE1SIZE*2];
43 unsigned char attribute; /* 0x0f for VFAT */
44 unsigned char hash1; /* Always 0? */
45 unsigned char sum; /* Checksum of short name */
46 unsigned char text2[VSE2SIZE*2];
47 unsigned char sector_l; /* 0 for VFAT */
48 unsigned char sector_u; /* 0 for VFAT */
49 unsigned char text3[VSE3SIZE*2];
56 wchar_t name[VBUFSIZE];
57 int status; /* is now a bit map of 32 bits */
58 unsigned int subentries;
59 unsigned char sum; /* no need to remember the sum for each entry,
60 * it is the same anyways */
64 const char *short_illegals=";+=[]',\"*\\<>/?:|";
65 const char *long_illegals = "\"*\\<>/?:|\005";
67 /* Automatically derive a new name */
68 static void autorename(char *name,
69 char tilda, char dot, const char *illegals,
73 unsigned int seqnum=0, maxseq=0;
78 printf("In autorename for name=%s.\n", name);
83 if (strchr(illegals, *p)) {
89 name[dotpos] && dotpos < limit && name[dotpos] != dot ;
91 if(name[dotpos] == tilda) {
95 } else if (name[dotpos] >= '0' && name[dotpos] <= '9') {
96 seqnum = seqnum * 10 + (uint8_t)(name[dotpos] - '0');
99 tildapos = -1; /* sequence number interrupted */
102 /* no sequence number yet */
103 if(dotpos > limit - 2) {
104 tildapos = limit - 2;
114 if(seqnum > 999999) {
116 tildapos = dotpos - 2;
117 /* this matches Win95's behavior, and also guarantees
118 * us that the sequence numbers never get shorter */
120 if (seqnum == maxseq) {
129 if((bump && seqnum == 1) || seqnum > 1 || mtools_numeric_tail)
130 sprintf(name+tildapos,"%c%d",tilda, seqnum);
133 /* replace the character if it wasn't a space */
135 printf("Out autorename for name=%s.\n", name);
140 void autorename_short(dos_name_t *name, int bump)
142 autorename(name->base, '~', ' ', short_illegals, 8, bump);
145 void autorename_long(char *name, int bump)
147 autorename(name, '-', '\0', long_illegals, 255, bump);
150 /* If null encountered, set *end to 0x40 and write nulls rest of way
151 * 950820: Win95 does not like this! It complains about bad characters.
152 * So, instead: If null encountered, set *end to 0x40, write the null, and
153 * write 0xff the rest of the way (that is what Win95 seems to do; hopefully
154 * that will make it happy)
156 /* Always return num */
157 static int unicode_write(wchar_t *in, unsigned char *out, int num, int *end_p)
161 for (j=0; j<num; ++j) {
164 out[0] = out[1] = 0xff;
166 /* TODO / FIXME : handle case where wchat has more
167 * than 2 bytes (i.e. bytes 2 or 3 are set.
168 * ==> generate surrogate pairs?
170 out[1] = (*in & 0xffff) >> 8;
185 static inline int unicode_read(unsigned char *in,
186 wchar_t *out, int num)
188 wchar_t *end_out = out+num;
190 while(out < end_out) {
192 *out = in[0] | ((in[1]) << 8);
207 static void clear_vfat(struct vfat_state *v)
217 * Calculate the checksum that results from the short name in *dir.
219 * The sum is formed by circularly right-shifting the previous sum
220 * and adding in each character, from left to right, padding both
221 * the name and extension to maximum length with spaces and skipping
222 * the "." (hence always summing exactly 11 characters).
224 * This exact algorithm is required in order to remain compatible
225 * with Microsoft Windows-95 and Microsoft Windows NT 3.5.
226 * Thanks to Jeffrey Richter of Microsoft Systems Journal for
227 * pointing me to the correct algorithm.
229 * David C. Niemi (niemi@tuxers.net) 95.01.19
231 static inline unsigned char sum_shortname(const dos_name_t *dn)
234 const char *name=dn->base;
235 const char *end = name+11;
237 for (sum=0; name<end; ++name)
238 sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1)
245 * Inspect a directory and any associated VSEs.
246 * Return 1 if the VSEs comprise a valid long file name,
249 static inline void check_vfat(struct vfat_state *v, struct directory *dir)
253 if (! v->subentries) {
255 fprintf(stderr, "check_vfat: no VSEs.\n");
260 memcpy(dn.base, (char *)dir->name, 8);
261 memcpy(dn.ext, (char *)dir->ext, 3);
263 if (v->sum != sum_shortname(&dn))
266 if( (v->status & ((1<<v->subentries) - 1)) != (1<<v->subentries) - 1)
267 return; /* missing entries */
269 /* zero out byte following last entry, for good measure */
270 v->name[VSE_NAMELEN * v->subentries] = 0;
274 unsigned int write_vfat(Stream_t *Dir, dos_name_t *shortname, char *longname,
276 direntry_t *mainEntry)
278 struct vfat_subentry *vse;
279 uint8_t vse_id, num_vses;
283 wchar_t unixyName[13];
284 doscp_t *cp = GET_DOSCONVERT(Dir);
286 wchar_t wlongname[MAX_VNAMELEN+1];
291 printf("Entering write_vfat with longname=\"%s\", start=%d.\n",
295 vse = (struct vfat_subentry *) &entry.dir;
296 /* Fill in invariant part of vse */
297 vse->attribute = 0x0f;
298 vse->hash1 = vse->sector_l = vse->sector_u = 0;
299 vse->sum = sum_shortname(shortname);
301 printf("Wrote checksum=%d for shortname %s.%s\n",
302 vse->sum,shortname->base,shortname->ext);
305 wlen = native_to_wchar(longname, wlongname, MAX_VNAMELEN+1,
307 num_vses = (uint8_t)((wlen + VSE_NAMELEN - 1)/VSE_NAMELEN);
308 for (vse_id = num_vses; vse_id; --vse_id) {
311 c = wlongname + (vse_id - 1) * VSE_NAMELEN;
313 c += unicode_write(c, vse->text1, VSE1SIZE, &end);
314 c += unicode_write(c, vse->text2, VSE2SIZE, &end);
315 c += unicode_write(c, vse->text3, VSE3SIZE, &end);
317 vse->id = (vse_id == num_vses) ? (vse_id | VSE_LAST) : vse_id;
319 printf("Writing longname=(%s), VSE %d (%13s) at %d, end = %d.\n",
320 longname, vse_id, longname + (vse_id-1) * VSE_NAMELEN,
321 start + num_vses - vse_id, start + num_vses);
324 setEntryToPos(&entry, start + num_vses - vse_id);
325 low_level_dir_write(&entry);
331 cache = allocDirCache(Dir, start + num_vses + 1);
333 fprintf(stderr, "Out of memory error\n");
336 unix_name(cp, shortname->base, shortname->ext, 0, unixyName);
337 addUsedEntry(cache, start, start + num_vses + 1, wlongname, unixyName,
339 low_level_dir_write(mainEntry);
340 return start + num_vses;
343 void dir_write(direntry_t *entry)
345 dirCacheEntry_t *dce;
348 if(isRootEntry(entry)) {
349 fprintf(stderr, "Attempt to write root directory pointer\n");
353 cache = allocDirCache(entry->Dir, getNextEntryAsPos(entry));
355 fprintf(stderr, "Out of memory error in dir_write\n");
358 dce = cache->entries[entry->entry];
360 if(entry->dir.name[0] == DELMARK) {
361 addFreeEntry(cache, dce->beginSlot, dce->endSlot);
363 dce->dir = entry->dir;
366 low_level_dir_write(entry);
371 * The following function translates a series of vfat_subentries into
372 * data suitable for a dircache entry
374 static inline void parse_vses(direntry_t *entry,
375 struct vfat_state *v)
377 struct vfat_subentry *vse;
378 unsigned char id, last_flag;
381 vse = (struct vfat_subentry *) &entry->dir;
383 id = vse->id & VSE_MASK;
384 last_flag = (vse->id & VSE_LAST);
385 if (id > MAX_VFAT_SUBENTRIES) {
386 fprintf(stderr, "parse_vses: invalid VSE ID %d at %d.\n",
391 /* 950819: This code enforced finding the VSEs in order. Well, Win95
392 * likes to write them in *reverse* order for some bizarre reason! So
393 * we pretty much have to tolerate them coming in any possible order.
394 * So skip this check, we'll do without it (What does this do, Alain?).
396 * 950820: Totally rearranged code to tolerate any order but to warn if
397 * they are not in reverse order like Win95 uses.
399 * 950909: Tolerate any order. We recognize new chains by mismatching
400 * checksums. In the event that the checksums match, new entries silently
401 * overwrite old entries of the same id. This should accept all valid
402 * entries, but may fail to reject invalid entries in some rare cases.
405 /* bad checksum, begin new chain */
406 if(v->sum != vse->sum) {
412 if(v->status & (1 << (id-1)))
414 "parse_vses: duplicate VSE %d\n", vse->id);
417 v->status |= 1 << (id-1);
422 if (id > v->subentries)
423 /* simple test to detect entries preceding
424 * the "last" entry (really the first) */
426 "parse_vses: new VSE %d sans LAST flag\n",
430 c = &(v->name[VSE_NAMELEN * (id-1)]);
431 c += unicode_read(vse->text1, c, VSE1SIZE);
432 c += unicode_read(vse->text2, c, VSE2SIZE);
433 c += unicode_read(vse->text3, c, VSE3SIZE);
435 printf("Read VSE %d at %d, subentries=%d, = (%13ls).\n",
436 id,entry->entry,v->subentries,&(v->name[VSE_NAMELEN * (id-1)]));
439 *c = '\0'; /* Null terminate long name */
443 * Read one complete entry from directory (main name plus any VSEs
446 static dirCacheEntry_t *vfat_lookup_loop_common(doscp_t *cp,
447 direntry_t *direntry,
449 int lookForFreeSpace,
453 unsigned int initpos = getNextEntryAsPos(direntry);
454 struct vfat_state vfat;
464 if(!dir_read(direntry, &error)){
469 addFreeEndEntry(cache, initpos,
470 getEntryAsPos(direntry),
472 return addEndEntry(cache, getEntryAsPos(direntry));
475 if (endmarkSeen || direntry->dir.name[0] == ENDMARK){
476 /* the end of the directory */
477 if(lookForFreeSpace) {
481 return addEndEntry(cache, getEntryAsPos(direntry));
483 if(direntry->dir.name[0] != DELMARK &&
484 direntry->dir.attr == 0x0f)
485 parse_vses(direntry, &vfat);
491 /* If we get here, it's a short name FAT entry, maybe erased.
492 * thus we should make sure that the vfat structure will be
493 * cleared before the next loop run */
496 if (direntry->dir.name[0] == DELMARK) {
497 return addFreeEntry(cache, initpos,
498 getNextEntryAsPos(direntry));
501 check_vfat(&vfat, &direntry->dir);
505 /* mark space between last entry and this one as free */
506 addFreeEntry(cache, initpos,
507 getEntryAsPos(direntry) - vfat.subentries);
509 if (direntry->dir.attr & 0x8){
510 /* Read entry as a label */
511 wchar_t *ptr = newfile;
512 if (direntry->dir.name[0] == '\x05') {
513 ptr += dos_to_wchar(cp, "\xE5", ptr, 1);
514 ptr += dos_to_wchar(cp, direntry->dir.name+1, ptr, 7);
516 ptr += dos_to_wchar(cp, direntry->dir.name, ptr, 8);
518 ptr += dos_to_wchar(cp, direntry->dir.ext, ptr, 3);
528 longname = vfat.name;
532 return addUsedEntry(cache, getEntryAsPos(direntry) - vfat.subentries,
533 getNextEntryAsPos(direntry), longname,
534 newfile, &direntry->dir);
537 static inline dirCacheEntry_t *vfat_lookup_loop_for_read(doscp_t *cp,
538 direntry_t *direntry,
542 int initpos = direntry->entry + 1;
543 dirCacheEntry_t *dce;
546 dce = cache->entries[initpos];
548 setEntryToPos(direntry, dce->endSlot - 1);
551 return vfat_lookup_loop_common(cp,
552 direntry, cache, 0, io_error);
557 typedef enum result_t {
570 static result_t checkNameForMatch(struct direntry_t *direntry,
571 dirCacheEntry_t *dce,
572 const wchar_t *filename,
585 fprintf(stderr, "Unexpected entry type %d\n",
591 direntry->dir = dce->dir;
593 /* make sure the entry is of an accepted type */
594 if((direntry->dir.attr & 0x8) && !(flags & ACCEPT_LABEL))
598 /*---------- multiple files ----------*/
599 if(!((flags & MATCH_ANY) ||
601 match(dce->longName, filename, direntry->name, 0, length)) ||
602 match(dce->shortName, filename, direntry->name, 1, length))) {
607 /* entry of non-requested type, has to come after name
608 * checking because of clash handling */
609 if(IS_DIR(direntry) && !(flags & ACCEPT_DIR)) {
610 if(!(flags & (ACCEPT_LABEL|MATCH_ANY|NO_MSG))) {
612 WCHAR_TO_NATIVE(dce->shortName,tmp,13);
613 fprintf(stderr, "Skipping \"%s\", is a directory\n",
619 if(!(direntry->dir.attr & (ATTR_LABEL | ATTR_DIR)) &&
620 !(flags & ACCEPT_PLAIN)) {
621 if(!(flags & (ACCEPT_LABEL|MATCH_ANY|NO_MSG))) {
623 WCHAR_TO_NATIVE(dce->shortName,tmp,13);
625 "Skipping \"%s\", is not a directory\n",
635 int vfat_lookup_zt(direntry_t *direntry, const char *filename,
636 int flags, char *shortname, size_t shortname_size,
637 char *longname, size_t longname_size) {
638 return vfat_lookup(direntry, filename, strlen(filename),
639 flags, shortname, shortname_size,
640 longname, longname_size);
644 * vfat_lookup looks for filenames in directory dir.
645 * if a name if found, it is returned in outname
646 * if applicable, the file is opened and its stream is returned in File
649 int vfat_lookup(direntry_t *direntry, const char *filename,
651 int flags, char *shortname, size_t shortname_size,
652 char *longname, size_t longname_size)
654 dirCacheEntry_t *dce;
658 wchar_t wfilename[MAX_VNAMELEN+1];
659 doscp_t *cp = GET_DOSCONVERT(direntry->Dir);
662 length = native_to_wchar(filename, wfilename, MAX_VNAMELEN,
667 if (isNotFound(direntry))
670 cache = allocDirCache(direntry->Dir, getNextEntryAsPos(direntry));
672 fprintf(stderr, "Out of memory error in vfat_lookup [0]\n");
677 dce = vfat_lookup_loop_for_read(cp, direntry, cache, &io_error);
681 fprintf(stderr, "Out of memory error in vfat_lookup\n");
684 result = checkNameForMatch(direntry, dce,
686 (int) length, flags);
687 } while(result == RES_NOMATCH);
689 if(result == RES_MATCH){
692 wchar_to_native(dce->longName, longname,
693 MAX_VNAMELEN, longname_size);
698 wchar_to_native(dce->shortName, shortname,
700 direntry->beginSlot = dce->beginSlot;
701 direntry->endSlot = dce->endSlot-1;
702 return 0; /* file found */
704 direntry->entry = NOT_FOUND_ENTRY;
705 return -1; /* no file found */
709 static inline dirCacheEntry_t *vfat_lookup_loop_for_insert(doscp_t *cp,
710 direntry_t *direntry,
711 unsigned int initpos,
714 dirCacheEntry_t *dce;
717 dce = cache->entries[initpos];
718 if(dce && dce->type != DCET_END) {
721 setEntryForIteration(direntry, initpos);
722 dce = vfat_lookup_loop_common(cp,
723 direntry, cache, 1, &io_error);
729 "Out of memory error in vfat_lookup_loop\n");
732 return cache->entries[initpos];
736 static void accountFreeSlots(struct scan_state *ssp, dirCacheEntry_t *dce)
741 if(ssp->free_end != dce->beginSlot) {
742 ssp->free_start = dce->beginSlot;
744 ssp->free_end = dce->endSlot;
746 if(ssp->free_end - ssp->free_start >= ssp->size_needed) {
748 ssp->slot = ssp->free_start + ssp->size_needed - 1;
752 static void clear_scan(wchar_t *longname, int use_longname,
753 struct scan_state *s)
755 s->shortmatch = s->longmatch = -1;
756 s->free_end = s->got_slots = s->free_start = 0;
758 if (use_longname & 1)
759 s->size_needed = (unsigned)
760 (1 + (wcslen(longname) + VSE_NAMELEN - 1)/VSE_NAMELEN);
765 /* lookup_for_insert replaces the old scandir function. It directly
766 * calls into vfat_lookup_loop, thus eliminating the overhead of the
769 int lookupForInsert(Stream_t *Dir,
770 struct direntry_t *direntry,
773 struct scan_state *ssp,
776 int pessimisticShortRename,
781 dirCacheEntry_t *dce;
783 unsigned int pos; /* position _before_ the next answered entry */
784 wchar_t shortName[13];
785 wchar_t wlongname[MAX_VNAMELEN+1];
786 doscp_t *cp = GET_DOSCONVERT(Dir);
788 native_to_wchar(longname, wlongname, MAX_VNAMELEN+1, 0, 0);
789 clear_scan(wlongname, use_longname, ssp);
791 ignore_match = (ignore_entry == -2 );
793 initializeDirentry(&entry, Dir);
796 /* hash bitmap of already encountered names. Speeds up batch appends
797 * to huge directories, because in the best case, we only need to scan
798 * the new entries rather than the whole directory */
799 cache = allocDirCache(Dir, 1);
801 fprintf(stderr, "Out of memory error in lookupForInsert\n");
806 unix_name(cp, dosname->base, dosname->ext, 0, shortName);
808 pos = cache->nrHashed;
809 if(source_entry >= 0 ||
810 (pos && isHashed(cache, wlongname))) {
812 } else if(pos && !ignore_match && isHashed(cache, shortName)) {
813 if(pessimisticShortRename) {
814 ssp->shortmatch = -2;
818 } else if(growDirCache(cache, pos) < 0) {
819 fprintf(stderr, "Out of memory error in vfat_looup [0]\n");
823 dce = vfat_lookup_loop_for_insert(cp, &entry, pos, cache);
826 accountFreeSlots(ssp, dce);
829 if(!(dce->dir.attr & 0x8) &&
830 (signed int)dce->endSlot-1 == source_entry)
831 accountFreeSlots(ssp, dce);
833 /* labels never match, neither does the
835 if( (dce->dir.attr & 0x8) ||
836 ((signed int)dce->endSlot-1==ignore_entry))
839 /* check long name */
841 !wcscasecmp(dce->longName, wlongname)) ||
843 !wcscasecmp(dce->shortName, wlongname))) {
845 (int) (dce->endSlot - 1);
846 /* long match is a reason for
848 direntry->beginSlot = dce->beginSlot;
849 direntry->endSlot = dce->endSlot - 1;
853 /* Long name or not, always check for
854 * short name match */
856 !wcscasecmp(shortName, dce->shortName))
858 (int) (dce->endSlot - 1);
864 } while(dce->type != DCET_END);
865 if (ssp->shortmatch > -1)
867 ssp->max_entry = dce->beginSlot;
869 return 6; /* Success */
871 /* Need more room. Can we grow the directory? */
873 return 5; /* OK, try to grow the directory */
875 fprintf(stderr, "No directory slots\n");