source: trunk/minix/commands/i386/mtools-3.9.7/vfat.c@ 15

Last change on this file since 15 was 9, checked in by Mattia Monga, 14 years ago

Minix 3.1.2a

File size: 17.4 KB
Line 
1/* vfat.c
2 *
3 * Miscellaneous VFAT-related functions
4 */
5
6#include "sysincludes.h"
7#include "msdos.h"
8#include "mtools.h"
9#include "vfat.h"
10#include "file.h"
11#include "dirCache.h"
12
13/* #define DEBUG */
14
15const char *short_illegals=";+=[]',\"*\\<>/?:|";
16const char *long_illegals = "\"*\\<>/?:|\005";
17
18/* Automatically derive a new name */
19static void autorename(char *name,
20 char tilda, char dot, const char *illegals,
21 int limit, int bump)
22{
23 int tildapos, dotpos;
24 unsigned int seqnum=0, maxseq=0;
25 char tmp;
26 char *p;
27
28#ifdef DEBUG
29 printf("In autorename for name=%s.\n", name);
30#endif
31 tildapos = -1;
32
33 for(p=name; *p ; p++)
34 if((*p < ' ' && *p != '\005') || strchr(illegals, *p)) {
35 *p = '_';
36 bump = 0;
37 }
38
39 for(dotpos=0;
40 name[dotpos] && dotpos < limit && name[dotpos] != dot ;
41 dotpos++) {
42 if(name[dotpos] == tilda) {
43 tildapos = dotpos;
44 seqnum = 0;
45 maxseq = 1;
46 } else if (name[dotpos] >= '0' && name[dotpos] <= '9') {
47 seqnum = seqnum * 10 + name[dotpos] - '0';
48 maxseq = maxseq * 10;
49 } else
50 tildapos = -1; /* sequence number interrupted */
51 }
52 if(tildapos == -1) {
53 /* no sequence number yet */
54 if(dotpos > limit - 2) {
55 tildapos = limit - 2;
56 dotpos = limit;
57 } else {
58 tildapos = dotpos;
59 dotpos += 2;
60 }
61 seqnum = 1;
62 } else {
63 if(bump)
64 seqnum++;
65 if(seqnum > 999999) {
66 seqnum = 1;
67 tildapos = dotpos - 2;
68 /* this matches Win95's behavior, and also guarantees
69 * us that the sequence numbers never get shorter */
70 }
71 if (seqnum == maxseq) {
72 if(dotpos >= limit)
73 tildapos--;
74 else
75 dotpos++;
76 }
77 }
78
79 tmp = name[dotpos];
80 if((bump && seqnum == 1) || seqnum > 1 || mtools_numeric_tail)
81 sprintf(name+tildapos,"%c%d",tilda, seqnum);
82 if(dot)
83 name[dotpos]=tmp;
84 /* replace the character if it wasn't a space */
85}
86
87
88void autorename_short(char *name, int bump)
89{
90 autorename(name, '~', ' ', short_illegals, 8, bump);
91}
92
93void autorename_long(char *name, int bump)
94{
95 autorename(name, '-', '\0', long_illegals, 255, bump);
96}
97
98
99static inline int unicode_read(struct unicode_char *in, char *out, int num)
100{
101 char *end_out = out+num;
102
103 while(out < end_out) {
104 if (in->uchar)
105 *out = '_';
106 else
107 *out = in->lchar;
108 ++out;
109 ++in;
110 }
111 return num;
112}
113
114
115void clear_vfat(struct vfat_state *v)
116{
117 v->subentries = 0;
118 v->status = 0;
119 v->present = 0;
120}
121
122
123/* sum_shortname
124 *
125 * Calculate the checksum that results from the short name in *dir.
126 *
127 * The sum is formed by circularly right-shifting the previous sum
128 * and adding in each character, from left to right, padding both
129 * the name and extension to maximum length with spaces and skipping
130 * the "." (hence always summing exactly 11 characters).
131 *
132 * This exact algorithm is required in order to remain compatible
133 * with Microsoft Windows-95 and Microsoft Windows NT 3.5.
134 * Thanks to Jeffrey Richter of Microsoft Systems Journal for
135 * pointing me to the correct algorithm.
136 *
137 * David C. Niemi (niemi@tux.org) 95.01.19
138 */
139static inline unsigned char sum_shortname(char *name)
140{
141 unsigned char sum;
142 char *end = name+11;
143
144 for (sum=0; name<end; ++name)
145 sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1)
146 + (*name ? *name : ' ');
147 return(sum);
148}
149
150/* check_vfat
151 *
152 * Inspect a directory and any associated VSEs.
153 * Return 1 if the VSEs comprise a valid long file name,
154 * 0 if not.
155 */
156static inline void check_vfat(struct vfat_state *v, struct directory *dir)
157{
158 char name[12];
159
160 if (! v->subentries) {
161#ifdef DEBUG
162 fprintf(stderr, "check_vfat: no VSEs.\n");
163#endif
164 return;
165 }
166
167 strncpy((char *)name, (char *)dir->name, 8);
168 strncpy((char *)name + 8, (char *)dir->ext, 3);
169 name[11] = '\0';
170
171 if (v->sum != sum_shortname(name))
172 return;
173
174 if( (v->status & ((1<<v->subentries) - 1)) != (1<<v->subentries) - 1)
175 return; /* missing entries */
176
177 /* zero out byte following last entry, for good measure */
178 v->name[VSE_NAMELEN * v->subentries] = 0;
179 v->present = 1;
180}
181
182
183int clear_vses(Stream_t *Dir, int entrySlot, size_t last)
184{
185 direntry_t entry;
186 dirCache_t *cache;
187 int error;
188
189 entry.Dir = Dir;
190 entry.entry = entrySlot;
191
192 /*maximize(last, entry.entry + MAX_VFAT_SUBENTRIES);*/
193 cache = allocDirCache(Dir, last);
194 if(!cache) {
195 fprintf(stderr, "Out of memory error in clear_vses\n");
196 exit(1);
197 }
198 addFreeEntry(cache, entry.entry, last);
199 for (; entry.entry < last; ++entry.entry) {
200#ifdef DEBUG
201 fprintf(stderr,"Clearing entry %d.\n", entry.entry);
202#endif
203 dir_read(&entry, &error);
204 if(error)
205 return error;
206 if(!entry.dir.name[0] || entry.dir.name[0] == DELMARK)
207 break;
208 entry.dir.name[0] = DELMARK;
209 if (entry.dir.attr == 0xf)
210 entry.dir.attr = '\0';
211 low_level_dir_write(&entry);
212 }
213 return 0;
214}
215
216int write_vfat(Stream_t *Dir, char *shortname, char *longname, int start,
217 direntry_t *mainEntry)
218{
219 struct vfat_subentry *vse;
220 int vse_id, num_vses;
221 char *c;
222 direntry_t entry;
223 dirCache_t *cache;
224 char unixyName[13];
225
226 if(longname) {
227#ifdef DEBUG
228 printf("Entering write_vfat with longname=\"%s\", start=%d.\n",
229 longname,start);
230#endif
231 entry.Dir = Dir;
232 vse = (struct vfat_subentry *) &entry.dir;
233 /* Fill in invariant part of vse */
234 vse->attribute = 0x0f;
235 vse->hash1 = vse->sector_l = vse->sector_u = 0;
236 vse->sum = sum_shortname(shortname);
237#ifdef DEBUG
238 printf("Wrote checksum=%d for shortname %s.\n",
239 vse->sum,shortname);
240#endif
241 num_vses = strlen(longname)/VSE_NAMELEN + 1;
242 for (vse_id = num_vses; vse_id; --vse_id) {
243 int end = 0;
244
245 c = longname + (vse_id - 1) * VSE_NAMELEN;
246
247 c += unicode_write(c, vse->text1, VSE1SIZE, &end);
248 c += unicode_write(c, vse->text2, VSE2SIZE, &end);
249 c += unicode_write(c, vse->text3, VSE3SIZE, &end);
250
251 vse->id = (vse_id == num_vses) ? (vse_id | VSE_LAST) : vse_id;
252#ifdef DEBUG
253 printf("Writing longname=(%s), VSE %d (%13s) at %d, end = %d.\n",
254 longname, vse_id, longname + (vse_id-1) * VSE_NAMELEN,
255 start + num_vses - vse_id, start + num_vses);
256#endif
257
258 entry.entry = start + num_vses - vse_id;
259 low_level_dir_write(&entry);
260 }
261 } else
262 num_vses = 0;
263 cache = allocDirCache(Dir, start + num_vses + 1);
264 if(!cache) {
265 fprintf(stderr, "Out of memory error\n");
266 exit(1);
267 }
268 unix_name(shortname, shortname+8, 0, unixyName);
269 addUsedEntry(cache, start, start + num_vses + 1, longname, unixyName,
270 &mainEntry->dir);
271 low_level_dir_write(mainEntry);
272 return start + num_vses;
273}
274
275void dir_write(direntry_t *entry)
276{
277 dirCacheEntry_t *dce;
278 dirCache_t *cache;
279
280 if(entry->entry == -3) {
281 fprintf(stderr, "Attempt to write root directory pointer\n");
282 exit(1);
283 }
284
285 cache = allocDirCache(entry->Dir, entry->entry + 1);
286 if(!cache) {
287 fprintf(stderr, "Out of memory error in dir_write\n");
288 exit(1);
289 }
290 dce = cache->entries[entry->entry];
291 if(dce) {
292 if(entry->dir.name[0] == DELMARK) {
293 addFreeEntry(cache, dce->beginSlot, dce->endSlot);
294 } else {
295 dce->dir = entry->dir;
296 }
297 }
298 low_level_dir_write(entry);
299}
300
301
302/*
303 * The following function translates a series of vfat_subentries into
304 * data suitable for a dircache entry
305 */
306static inline void parse_vses(direntry_t *entry,
307 struct vfat_state *v)
308{
309 struct vfat_subentry *vse;
310 unsigned char id, last_flag;
311 char *c;
312
313 vse = (struct vfat_subentry *) &entry->dir;
314
315 id = vse->id & VSE_MASK;
316 last_flag = (vse->id & VSE_LAST);
317 if (id > MAX_VFAT_SUBENTRIES) {
318 fprintf(stderr, "parse_vses: invalid VSE ID %d at %d.\n",
319 id, entry->entry);
320 return;
321 }
322
323/* 950819: This code enforced finding the VSEs in order. Well, Win95
324 * likes to write them in *reverse* order for some bizarre reason! So
325 * we pretty much have to tolerate them coming in any possible order.
326 * So skip this check, we'll do without it (What does this do, Alain?).
327 *
328 * 950820: Totally rearranged code to tolerate any order but to warn if
329 * they are not in reverse order like Win95 uses.
330 *
331 * 950909: Tolerate any order. We recognize new chains by mismatching
332 * checksums. In the event that the checksums match, new entries silently
333 * overwrite old entries of the same id. This should accept all valid
334 * entries, but may fail to reject invalid entries in some rare cases.
335 */
336
337 /* bad checksum, begin new chain */
338 if(v->sum != vse->sum) {
339 clear_vfat(v);
340 v->sum = vse->sum;
341 }
342
343#ifdef DEBUG
344 if(v->status & (1 << (id-1)))
345 fprintf(stderr,
346 "parse_vses: duplicate VSE %d\n", vse->id);
347#endif
348
349 v->status |= 1 << (id-1);
350 if(last_flag)
351 v->subentries = id;
352
353#ifdef DEBUG
354 if (id > v->subentries)
355 /* simple test to detect entries preceding
356 * the "last" entry (really the first) */
357 fprintf(stderr,
358 "parse_vses: new VSE %d sans LAST flag\n",
359 vse->id);
360#endif
361
362 c = &(v->name[VSE_NAMELEN * (id-1)]);
363 c += unicode_read(vse->text1, c, VSE1SIZE);
364 c += unicode_read(vse->text2, c, VSE2SIZE);
365 c += unicode_read(vse->text3, c, VSE3SIZE);
366#ifdef DEBUG
367 printf("Read VSE %d at %d, subentries=%d, = (%13s).\n",
368 id,entry->entry,v->subentries,&(v->name[VSE_NAMELEN * (id-1)]));
369#endif
370 if (last_flag)
371 *c = '\0'; /* Null terminate long name */
372}
373
374
375static dirCacheEntry_t *vfat_lookup_loop_common(direntry_t *direntry,
376 dirCache_t *cache,
377 int lookForFreeSpace,
378 int *io_error)
379{
380 char newfile[13];
381 int initpos = direntry->entry + 1;
382 struct vfat_state vfat;
383 char *longname;
384 int error;
385
386 /* not yet cached */
387 *io_error = 0;
388 clear_vfat(&vfat);
389 while(1) {
390 ++direntry->entry;
391 if(!dir_read(direntry, &error)){
392 if(error) {
393 *io_error = error;
394 return NULL;
395 }
396 addFreeEntry(cache, initpos, direntry->entry);
397 return addEndEntry(cache, direntry->entry);
398 }
399
400 if (direntry->dir.name[0] == '\0'){
401 /* the end of the directory */
402 if(lookForFreeSpace)
403 continue;
404 return addEndEntry(cache, direntry->entry);
405 }
406 if(direntry->dir.name[0] != DELMARK &&
407 direntry->dir.attr == 0x0f)
408 parse_vses(direntry, &vfat);
409 else
410 /* the main entry */
411 break;
412 }
413
414 /* If we get here, it's a short name FAT entry, maybe erased.
415 * thus we should make sure that the vfat structure will be
416 * cleared before the next loop run */
417
418 /* deleted file */
419 if (direntry->dir.name[0] == DELMARK) {
420 return addFreeEntry(cache, initpos,
421 direntry->entry + 1);
422 }
423
424 check_vfat(&vfat, &direntry->dir);
425 if(!vfat.present)
426 vfat.subentries = 0;
427
428 /* mark space between last entry and this one as free */
429 addFreeEntry(cache, initpos,
430 direntry->entry - vfat.subentries);
431
432 if (direntry->dir.attr & 0x8){
433 strncpy(newfile, direntry->dir.name,8);
434 newfile[8]='\0';
435 strncat(newfile, direntry->dir.ext,3);
436 newfile[11]='\0';
437 } else
438 unix_name(direntry->dir.name,
439 direntry->dir.ext,
440 direntry->dir.Case,
441 newfile);
442
443 if(vfat.present)
444 longname = vfat.name;
445 else
446 longname = 0;
447
448 return addUsedEntry(cache, direntry->entry - vfat.subentries,
449 direntry->entry + 1, longname,
450 newfile, &direntry->dir);
451}
452
453static inline dirCacheEntry_t *vfat_lookup_loop_for_read(direntry_t *direntry,
454 dirCache_t *cache,
455 int *io_error)
456{
457 int initpos = direntry->entry + 1;
458 dirCacheEntry_t *dce;
459
460 *io_error = 0;
461 dce = cache->entries[initpos];
462 if(dce) {
463 direntry->entry = dce->endSlot - 1;
464 return dce;
465 } else {
466 return vfat_lookup_loop_common(direntry, cache, 0, io_error);
467 }
468}
469
470
471typedef enum result_t {
472 RES_NOMATCH,
473 RES_MATCH,
474 RES_END,
475 RES_ERROR
476} result_t;
477
478
479/*
480 * 0 does not match
481 * 1 matches
482 * 2 end
483 */
484static result_t checkNameForMatch(struct direntry_t *direntry,
485 dirCacheEntry_t *dce,
486 const char *filename,
487 char *longname,
488 char *shortname,
489 int length,
490 int flags)
491{
492 switch(dce->type) {
493 case DCET_FREE:
494 return RES_NOMATCH;
495 case DCET_END:
496 return RES_END;
497 case DCET_USED:
498 break;
499 default:
500 fprintf(stderr, "Unexpected entry type %d\n",
501 dce->type);
502 return RES_ERROR;
503 }
504
505 direntry->dir = dce->dir;
506
507 /* make sure the entry is of an accepted type */
508 if((direntry->dir.attr & 0x8) && !(flags & ACCEPT_LABEL))
509 return RES_NOMATCH;
510
511
512 /*---------- multiple files ----------*/
513 if(!((flags & MATCH_ANY) ||
514 (dce->longName &&
515 match(dce->longName, filename, direntry->name, 0, length)) ||
516 match(dce->shortName, filename, direntry->name, 1, length))) {
517
518 return RES_NOMATCH;
519 }
520
521 /* entry of non-requested type, has to come after name
522 * checking because of clash handling */
523 if(IS_DIR(direntry) && !(flags & ACCEPT_DIR)) {
524 if(!(flags & (ACCEPT_LABEL|MATCH_ANY|NO_MSG)))
525 fprintf(stderr,
526 "Skipping \"%s\", is a directory\n",
527 dce->shortName);
528 return RES_NOMATCH;
529 }
530
531 if(!(direntry->dir.attr & (ATTR_LABEL | ATTR_DIR)) &&
532 !(flags & ACCEPT_PLAIN)) {
533 if(!(flags & (ACCEPT_LABEL|MATCH_ANY|NO_MSG)))
534 fprintf(stderr,
535 "Skipping \"%s\", is not a directory\n",
536 dce->shortName);
537 return RES_NOMATCH;
538 }
539
540 return RES_MATCH;
541}
542
543
544/*
545 * vfat_lookup looks for filenames in directory dir.
546 * if a name if found, it is returned in outname
547 * if applicable, the file is opened and its stream is returned in File
548 */
549
550int vfat_lookup(direntry_t *direntry, const char *filename, int length,
551 int flags, char *shortname, char *longname)
552{
553 dirCacheEntry_t *dce;
554 result_t result;
555 dirCache_t *cache;
556 int io_error;
557
558 if(length == -1 && filename)
559 length = strlen(filename);
560
561 if (direntry->entry == -2)
562 return -1;
563
564 cache = allocDirCache(direntry->Dir, direntry->entry+1);
565 if(!cache) {
566 fprintf(stderr, "Out of memory error in vfat_lookup [0]\n");
567 exit(1);
568 }
569
570 do {
571 dce = vfat_lookup_loop_for_read(direntry, cache, &io_error);
572 if(!dce) {
573 if (io_error)
574 return -2;
575 fprintf(stderr, "Out of memory error in vfat_lookup\n");
576 exit(1);
577 }
578 result = checkNameForMatch(direntry, dce,
579 filename,
580 longname, shortname,
581 length, flags);
582 } while(result == RES_NOMATCH);
583
584 if(result == RES_MATCH){
585 if(longname){
586 if(dce->longName)
587 strcpy(longname, dce->longName);
588 else
589 *longname ='\0';
590 }
591 if(shortname)
592 strcpy(shortname, dce->shortName);
593 direntry->beginSlot = dce->beginSlot;
594 direntry->endSlot = dce->endSlot-1;
595 return 0; /* file found */
596 } else {
597 direntry->entry = -2;
598 return -1; /* no file found */
599 }
600}
601
602static inline dirCacheEntry_t *vfat_lookup_loop_for_insert(direntry_t *direntry,
603 int initpos,
604 dirCache_t *cache)
605{
606 dirCacheEntry_t *dce;
607 int io_error;
608
609 dce = cache->entries[initpos];
610 if(dce && dce->type != DCET_END) {
611 return dce;
612 } else {
613 direntry->entry = initpos - 1;
614 dce = vfat_lookup_loop_common(direntry, cache, 1, &io_error);
615 if(!dce) {
616 if (io_error) {
617 return NULL;
618 }
619 fprintf(stderr,
620 "Out of memory error in vfat_lookup_loop\n");
621 exit(1);
622 }
623 return cache->entries[initpos];
624 }
625}
626
627static void accountFreeSlots(struct scan_state *ssp, dirCacheEntry_t *dce)
628{
629 if(ssp->got_slots)
630 return;
631
632 if(ssp->free_end != dce->beginSlot) {
633 ssp->free_start = dce->beginSlot;
634 }
635 ssp->free_end = dce->endSlot;
636
637 if(ssp->free_end - ssp->free_start >= ssp->size_needed) {
638 ssp->got_slots = 1;
639 ssp->slot = ssp->free_start + ssp->size_needed - 1;
640 }
641}
642
643/* lookup_for_insert replaces the old scandir function. It directly
644 * calls into vfat_lookup_loop, thus eliminating the overhead of the
645 * normal vfat_lookup
646 */
647int lookupForInsert(Stream_t *Dir,
648 char *dosname,
649 char *longname,
650 struct scan_state *ssp,
651 int ignore_entry,
652 int source_entry,
653 int pessimisticShortRename)
654{
655 direntry_t entry;
656 int ignore_match;
657 dirCacheEntry_t *dce;
658 dirCache_t *cache;
659 int pos; /* position _before_ the next answered entry */
660 char shortName[13];
661
662 ignore_match = (ignore_entry == -2 );
663
664 initializeDirentry(&entry, Dir);
665 ssp->match_free = 0;
666
667 /* hash bitmap of already encountered names. Speeds up batch appends
668 * to huge directories, because in the best case, we only need to scan
669 * the new entries rather than the whole directory */
670 cache = allocDirCache(Dir, 1);
671 if(!cache) {
672 fprintf(stderr, "Out of memory error in lookupForInsert\n");
673 exit(1);
674 }
675
676 if(!ignore_match)
677 unix_name(dosname, dosname + 8, 0, shortName);
678
679 pos = cache->nrHashed;
680 if(source_entry >= 0 ||
681 (pos && isHashed(cache, longname))) {
682 pos = 0;
683 } else if(pos && !ignore_match && isHashed(cache, shortName)) {
684 if(pessimisticShortRename) {
685 ssp->shortmatch = -2;
686 return 1;
687 }
688 pos = 0;
689 } else if(growDirCache(cache, pos) < 0) {
690 fprintf(stderr, "Out of memory error in vfat_looup [0]\n");
691 exit(1);
692 }
693 do {
694 dce = vfat_lookup_loop_for_insert(&entry, pos, cache);
695 switch(dce->type) {
696 case DCET_FREE:
697 accountFreeSlots(ssp, dce);
698 break;
699 case DCET_USED:
700 if(!(dce->dir.attr & 0x8) &&
701 dce->endSlot - 1 == source_entry)
702 accountFreeSlots(ssp, dce);
703
704 /* labels never match, neither does the
705 * ignored entry */
706 if( (dce->dir.attr & 0x8) ||
707 (dce->endSlot - 1 == ignore_entry) )
708 break;
709
710 /* check long name */
711 if((dce->longName &&
712 !strcasecmp(dce->longName, longname)) ||
713 (dce->shortName &&
714 !strcasecmp(dce->shortName, longname))) {
715 ssp->longmatch = dce->endSlot - 1;
716 /* long match is a reason for
717 * immediate stop */
718 return 1;
719 }
720
721 /* Long name or not, always check for
722 * short name match */
723 if (!ignore_match &&
724 !strcasecmp(shortName, dce->shortName))
725 ssp->shortmatch = dce->endSlot - 1;
726 break;
727 case DCET_END:
728 break;
729 }
730 pos = dce->endSlot;
731 } while(dce->type != DCET_END);
732 if (ssp->shortmatch > -1)
733 return 1;
734 ssp->max_entry = dce->beginSlot;
735 if (ssp->got_slots)
736 return 6; /* Success */
737
738 /* Need more room. Can we grow the directory? */
739 if(!isRootDir(Dir))
740 return 5; /* OK, try to grow the directory */
741
742 fprintf(stderr, "No directory slots\n");
743 return -1;
744}
745
746
747
748/* End vfat.c */
Note: See TracBrowser for help on using the repository browser.