Initial import package mtools: Programs for accessing MS-DOS disks without mounting...
[profile/ivi/mtools.git] / config.c
1 /*  Copyright 1996-2005,2007-2009 Alain Knaff.
2  *  This file is part of mtools.
3  *
4  *  Mtools is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  Mtools is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with Mtools.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  */
18 #include "sysincludes.h"
19 #include "mtools.h"
20 #include "codepage.h"
21 #include "mtoolsPaths.h"
22
23 /* global variables */
24 /* they are not really harmful here, because there is only one configuration
25  * file per invocations */
26
27 #define MAX_LINE_LEN 256
28
29 /* scanner */
30 static char buffer[MAX_LINE_LEN+1]; /* buffer for the whole line */
31 static char *pos; /* position in line */
32 static char *token; /* last scanned token */
33 static size_t token_length; /* length of the token */
34 static FILE *fp; /* file pointer for configuration file */
35 static int linenumber; /* current line number. Only used for printing
36                                                 * error messages */
37 static int lastTokenLinenumber; /* line numnber for last token */
38 static const char *filename=NULL; /* current file name. Used for printing
39                                    * error messages, and for storing in
40                                    * the device definition (mtoolstest) */
41 static int file_nr=0;
42
43
44 static int flag_mask; /* mask of currently set flags */
45
46 /* devices */
47 static int cur_devs; /* current number of defined devices */
48 static int cur_dev; /* device being filled in. If negative, none */
49 static int trusted=0; /* is the currently parsed device entry trusted? */
50 static int nr_dev; /* number of devices that the current table can hold */
51 struct device *devices; /* the device table */
52 static int token_nr; /* number of tokens in line */
53
54 static char default_drive='\0'; /* default drive */
55
56 /* "environment" variables */
57 unsigned int mtools_skip_check=0;
58 unsigned int mtools_fat_compatibility=0;
59 unsigned int mtools_ignore_short_case=0;
60 unsigned int mtools_rate_0=0;
61 unsigned int mtools_rate_any=0;
62 unsigned int mtools_no_vfat=0;
63 unsigned int mtools_numeric_tail=1;
64 unsigned int mtools_dotted_dir=0;
65 unsigned int mtools_twenty_four_hour_clock=1;
66 unsigned int mtools_default_codepage=850;
67 const char *mtools_date_string="yyyy-mm-dd";
68 char *country_string=0;
69
70 typedef struct switches_l {
71     const char *name;
72     caddr_t address;
73     enum {
74         T_INT,
75         T_STRING,
76         T_UINT
77     } type;
78 } switches_t;
79
80 static switches_t global_switches[] = {
81     { "MTOOLS_LOWER_CASE", (caddr_t) & mtools_ignore_short_case, T_UINT },
82     { "MTOOLS_FAT_COMPATIBILITY", (caddr_t) & mtools_fat_compatibility, T_UINT },
83     { "MTOOLS_SKIP_CHECK", (caddr_t) & mtools_skip_check, T_UINT },
84     { "MTOOLS_NO_VFAT", (caddr_t) & mtools_no_vfat, T_UINT },
85     { "MTOOLS_RATE_0", (caddr_t) &mtools_rate_0, T_UINT },
86     { "MTOOLS_RATE_ANY", (caddr_t) &mtools_rate_any, T_UINT },
87     { "MTOOLS_NAME_NUMERIC_TAIL", (caddr_t) &mtools_numeric_tail, T_UINT },
88     { "MTOOLS_DOTTED_DIR", (caddr_t) &mtools_dotted_dir, T_UINT },
89     { "MTOOLS_TWENTY_FOUR_HOUR_CLOCK",
90       (caddr_t) &mtools_twenty_four_hour_clock, T_UINT },
91     { "MTOOLS_DATE_STRING",
92       (caddr_t) &mtools_date_string, T_STRING },
93     { "DEFAULT_CODEPAGE", (caddr_t) &mtools_default_codepage, T_UINT }
94 };
95
96 typedef struct {
97     const char *name;
98     int flag;
99 } flags_t;
100
101 static flags_t openflags[] = {
102 #ifdef O_SYNC
103     { "sync",           O_SYNC },
104 #endif
105 #ifdef O_NDELAY
106     { "nodelay",        O_NDELAY },
107 #endif
108 #ifdef O_EXCL
109     { "exclusive",      O_EXCL },
110 #endif
111     { "none", 0 }  /* hack for those compilers that choke on commas
112                     * after the last element of an array */
113 };
114
115 static flags_t misc_flags[] = {
116 #ifdef USE_XDF
117     { "use_xdf",                USE_XDF_FLAG },
118 #endif
119     { "scsi",                   SCSI_FLAG },
120     { "nolock",                 NOLOCK_FLAG },
121     { "mformat_only",   MFORMAT_ONLY_FLAG },
122     { "filter",                 FILTER_FLAG },
123     { "privileged",             PRIV_FLAG },
124     { "vold",                   VOLD_FLAG },
125     { "remote",                 FLOPPYD_FLAG },
126     { "swap",                   SWAP_FLAG },
127 };
128
129 static struct {
130     const char *name;
131     signed char fat_bits;
132     int tracks;
133     unsigned short heads;
134     unsigned short sectors;
135 } default_formats[] = {
136     { "hd514",                  12, 80, 2, 15 },
137     { "high-density-5-1/4",     12, 80, 2, 15 },
138     { "1.2m",                   12, 80, 2, 15 },
139
140     { "hd312",                  12, 80, 2, 18 },
141     { "high-density-3-1/2",     12, 80, 2, 18 },
142     { "1.44m",                  12, 80, 2, 18 },
143
144     { "dd312",                  12, 80, 2, 9 },
145     { "double-density-3-1/2",   12, 80, 2, 9 },
146     { "720k",                   12, 80, 2, 9 },
147
148     { "dd514",                  12, 40, 2, 9 },
149     { "double-density-5-1/4",   12, 40, 2, 9 },
150     { "360k",                   12, 40, 2, 9 },
151
152     { "320k",                   12, 40, 2, 8 },
153     { "180k",                   12, 40, 1, 9 },
154     { "160k",                   12, 40, 1, 8 }
155 };
156
157 #define OFFS(x) ((caddr_t)&((struct device *)0)->x)
158
159 static switches_t dswitches[]= {
160     { "FILE", OFFS(name), T_STRING },
161     { "OFFSET", OFFS(offset), T_UINT },
162     { "PARTITION", OFFS(partition), T_UINT },
163     { "FAT", OFFS(fat_bits), T_INT },
164     { "FAT_BITS", OFFS(fat_bits), T_UINT },
165     { "MODE", OFFS(mode), T_UINT },
166     { "TRACKS",  OFFS(tracks), T_UINT },
167     { "CYLINDERS",  OFFS(tracks), T_UINT },
168     { "HEADS", OFFS(heads), T_UINT },
169     { "SECTORS", OFFS(sectors), T_UINT },
170     { "HIDDEN", OFFS(hidden), T_UINT },
171     { "PRECMD", OFFS(precmd), T_STRING },
172     { "BLOCKSIZE", OFFS(blocksize), T_UINT },
173     { "CODEPAGE", OFFS(codepage), T_UINT }
174 };
175
176 static void maintain_default_drive(char drive)
177 {
178     if(default_drive == ':')
179         return; /* we have an image */
180     if(default_drive == '\0' ||
181        default_drive > drive)
182         default_drive = drive;
183 }
184
185 char get_default_drive(void)
186 {
187     if(default_drive != '\0')
188         return default_drive;
189     else
190         return 'A';
191 }
192
193 static void syntax(const char *msg, int thisLine)
194 {
195     char drive='\0';
196     if(thisLine)
197         lastTokenLinenumber = linenumber;
198     if(cur_dev >= 0)
199         drive = devices[cur_dev].drive;
200     fprintf(stderr,"Syntax error at line %d ", lastTokenLinenumber);
201     if(drive) fprintf(stderr, "for drive %c: ", drive);
202     if(token) fprintf(stderr, "column %ld ", (long)(token - buffer));
203     fprintf(stderr, "in file %s: %s\n", filename, msg);
204     exit(1);
205 }
206
207 static void get_env_conf(void)
208 {
209     char *s;
210     unsigned int i;
211
212     for(i=0; i< sizeof(global_switches) / sizeof(*global_switches); i++) {
213         s = getenv(global_switches[i].name);
214         if(s) {
215             if(global_switches[i].type == T_INT)
216                 * ((int *)global_switches[i].address) = (int) strtol(s,0,0);
217             if(global_switches[i].type == T_UINT)
218                 * ((int *)global_switches[i].address) = (unsigned int) strtoul(s,0,0);
219             else if (global_switches[i].type == T_STRING)
220                 * ((char **)global_switches[i].address) = s;
221         }
222     }
223 }
224
225 static int mtools_getline(void)
226 {
227     if(!fp || !fgets(buffer, MAX_LINE_LEN, fp))
228         return -1;
229     linenumber++;
230     pos = buffer;
231     token_nr = 0;
232     buffer[MAX_LINE_LEN] = '\0';
233     if(strlen(buffer) == MAX_LINE_LEN)
234         syntax("line too long", 1);
235     return 0;
236 }
237                 
238 static void skip_junk(int expect)
239 {
240     lastTokenLinenumber = linenumber;
241     while(!pos || !*pos || strchr(" #\n\t", *pos)) {
242         if(!pos || !*pos || *pos == '#') {
243             if(mtools_getline()) {
244                 pos = 0;
245                 if(expect)
246                     syntax("end of file unexpected", 1);
247                 return;
248             }
249         } else
250             pos++;
251     }
252     token_nr++;
253 }
254
255 /* get the next token */
256 static char *get_next_token(void)
257 {
258     skip_junk(0);
259     if(!pos) {
260         token_length = 0;
261         token = 0;
262         return 0;
263     }
264     token = pos;
265     token_length = strcspn(token, " \t\n#:=");
266     pos += token_length;
267     return token;
268 }
269
270 static int match_token(const char *template)
271 {
272     return (strlen(template) == token_length &&
273             !strncasecmp(template, token, token_length));
274 }
275
276 static void expect_char(char c)
277 {
278     char buf[11];
279
280     skip_junk(1);
281     if(*pos != c) {
282         sprintf(buf, "expected %c", c);
283         syntax(buf, 1);
284     }
285     pos++;
286 }
287
288 static char *get_string(void)
289 {
290     char *end, *str;
291
292     skip_junk(1);
293     if(*pos != '"')
294         syntax(" \" expected", 0);
295     str = pos+1;
296     end = strchr(str, '\"');
297     if(!end)
298         syntax("unterminated string constant", 1);
299     *end = '\0';
300     pos = end+1;
301     return str;
302 }
303
304 static unsigned int get_unumber(void)
305 {
306     char *last;
307     unsigned int n;
308
309     skip_junk(1);
310     last = pos;
311     n=(unsigned int) strtoul(pos, &pos, 0);
312     if(last == pos)
313         syntax("numeral expected", 0);
314     pos++;
315     token_nr++;
316     return n;
317 }
318
319 static unsigned int get_number(void)
320 {
321     char *last;
322     int n;
323
324     skip_junk(1);
325     last = pos;
326     n=(int) strtol(pos, &pos, 0);
327     if(last == pos)
328         syntax("numeral expected", 0);
329     pos++;
330     token_nr++;
331     return n;
332 }
333
334 /* purge all entries pertaining to a given drive from the table */
335 static void purge(char drive, int fn)
336 {
337     int i,j;
338
339     drive = toupper(drive);
340     for(j=0, i=0; i < cur_devs; i++) {
341         if(devices[i].drive != drive ||
342            devices[i].file_nr == fn)
343             devices[j++] = devices[i];
344     }
345     cur_devs = j;
346 }
347
348 static void grow(void)
349 {
350     if(cur_devs >= nr_dev - 2) {
351         nr_dev = (cur_devs + 2) << 1;
352         if(!(devices=Grow(devices, nr_dev, struct device))){
353             printOom();
354             exit(1);
355         }
356     }
357 }
358         
359
360 static void init_drive(void)
361 {
362     memset((char *)&devices[cur_dev], 0, sizeof(struct device));
363     devices[cur_dev].ssize = 2;
364 }
365
366 /* prepends a device to the table */
367 static void prepend(void)
368 {
369     int i;
370
371     grow();
372     for(i=cur_devs; i>0; i--)
373         devices[i] = devices[i-1];
374     cur_dev = 0;
375     cur_devs++;
376     init_drive();
377 }
378
379
380 /* appends a device to the table */
381 static void append(void)
382 {
383     grow();
384     cur_dev = cur_devs;
385     cur_devs++;
386     init_drive();
387 }
388
389
390 static void finish_drive_clause(void)
391 {
392     char drive;
393     if(cur_dev == -1) {
394         trusted = 0;
395         return;
396     }
397     drive = devices[cur_dev].drive;
398     if(!devices[cur_dev].name)
399         syntax("missing filename", 0);
400     if(devices[cur_dev].tracks ||
401        devices[cur_dev].heads ||
402        devices[cur_dev].sectors) {
403         if(!devices[cur_dev].tracks ||
404            !devices[cur_dev].heads ||
405            !devices[cur_dev].sectors)
406             syntax("incomplete geometry: either indicate all of track/heads/sectors or none of them", 0);
407         if(!(devices[cur_dev].misc_flags &
408              (MFORMAT_ONLY_FLAG | FILTER_FLAG)))
409             syntax("if you supply a geometry, you also must supply one of the `mformat_only' or `filter' flags", 0);
410     }
411     devices[cur_dev].file_nr = file_nr;
412     devices[cur_dev].cfg_filename = filename;
413     if(! (flag_mask & PRIV_FLAG) && IS_SCSI(&devices[cur_dev]))
414         devices[cur_dev].misc_flags |= PRIV_FLAG;
415     if(!trusted && (devices[cur_dev].misc_flags & PRIV_FLAG)) {
416         fprintf(stderr,
417                 "Warning: privileged flag ignored for drive %c: defined in file %s\n",
418                 toupper(devices[cur_dev].drive), filename);
419         devices[cur_dev].misc_flags &= ~PRIV_FLAG;
420     }
421     trusted = 0;
422     cur_dev = -1;
423 }
424
425 static int set_var(struct switches_l *switches, int nr,
426                    caddr_t base_address)
427 {
428     int i;
429     for(i=0; i < nr; i++) {
430         if(match_token(switches[i].name)) {
431             expect_char('=');
432             if(switches[i].type == T_UINT)
433                 * ((int *)((long)switches[i].address+base_address)) =
434                     get_unumber();
435             if(switches[i].type == T_INT)
436                 * ((int *)((long)switches[i].address+base_address)) =
437                     get_number();
438             else if (switches[i].type == T_STRING)
439                 * ((char**)((long)switches[i].address+base_address))=
440                     strdup(get_string());
441             return 0;
442         }
443     }
444     return 1;
445 }
446
447 static int set_openflags(struct device *dev)
448 {
449     unsigned int i;
450
451     for(i=0; i < sizeof(openflags) / sizeof(*openflags); i++) {
452         if(match_token(openflags[i].name)) {
453             dev->mode |= openflags[i].flag;
454             return 0;
455         }
456     }
457     return 1;
458 }
459
460 static int set_misc_flags(struct device *dev)
461 {
462     unsigned int i;
463
464     for(i=0; i < sizeof(misc_flags) / sizeof(*misc_flags); i++) {
465         if(match_token(misc_flags[i].name)) {
466             flag_mask |= misc_flags[i].flag;
467             skip_junk(0);
468             if(pos && *pos == '=') {
469                 pos++;
470                 switch(get_number()) {
471                     case 0:
472                         return 0;
473                     case 1:
474                         break;
475                     default:
476                         syntax("expected 0 or 1", 0);
477                 }
478             }
479             dev->misc_flags |= misc_flags[i].flag;
480             return 0;
481         }
482     }
483     return 1;
484 }
485
486 static int set_def_format(struct device *dev)
487 {
488     unsigned int i;
489
490     for(i=0; i < sizeof(default_formats)/sizeof(*default_formats); i++) {
491         if(match_token(default_formats[i].name)) {
492             if(!dev->ssize)
493                 dev->ssize = 2;
494             if(!dev->tracks)
495                 dev->tracks = default_formats[i].tracks;
496             if(!dev->heads)
497                 dev->heads = default_formats[i].heads;
498             if(!dev->sectors)
499                 dev->sectors = default_formats[i].sectors;
500             if(!dev->fat_bits)
501                 dev->fat_bits = default_formats[i].fat_bits;
502             return 0;
503         }
504     }
505     return 1;
506 }
507
508 static int parse_one(int privilege);
509
510 /* check for offset embedded in file name, in the form file@@offset[SKMG] */
511 static off_t get_offset(char *name) {
512   char s, *ofsp, *endp = NULL;
513   off_t ofs;
514
515   ofsp = strstr(devices[cur_dev].name, "@@");
516   if (ofsp == NULL)
517     return 0; /* no separator */
518   ofs = strtol(ofsp+2, &endp, 0);
519   if (ofs <= 0)
520     return 0; /* invalid or missing offset */
521   s = *endp++;
522   if (s) {   /* trailing char, see if it is a size specifier */
523     endp++;
524     if (s == 's' || s == 'S')       /* sector */
525       ofs <<= 9;
526     else if (s == 'k' || s == 'K')  /* kb */
527       ofs <<= 10;
528     else if (s == 'm' || s == 'M')  /* Mb */
529       ofs <<= 20;
530     else if (s == 'g' || s == 'G')  /* Gb */
531       ofs <<= 30;
532     else
533       return 0;      /* invalid character */
534     if (*endp)
535       return 0;      /* extra char, invalid */
536   }
537   *ofsp = '\0';                              /* truncate file name */
538   return ofs;
539 }
540
541 void set_cmd_line_image(char *img, int flags) {
542   char *name;
543   prepend();
544   devices[cur_dev].drive = ':';
545   default_drive = ':';
546   devices[cur_dev].name = name = strdup(img);
547   devices[cur_dev].fat_bits = 0;
548   devices[cur_dev].tracks = 0;
549   devices[cur_dev].heads = 0;
550   devices[cur_dev].sectors = 0;
551   devices[cur_dev].offset = get_offset(name);
552   if (strchr(devices[cur_dev].name, '|')) {
553     char *pipechar = strchr(devices[cur_dev].name, '|');
554     *pipechar = 0;
555     strncpy(buffer, pipechar+1, MAX_LINE_LEN);
556     buffer[MAX_LINE_LEN] = '\0';
557     fp = NULL;
558     filename = "{command line}";
559     linenumber = 0;
560     lastTokenLinenumber = 0;
561     pos = buffer;
562     token = 0;
563     while (parse_one(0));
564   }
565 }
566
567 static void parse_old_device_line(char drive)
568 {
569     char name[MAXPATHLEN];
570     int items;
571     long offset;
572
573     /* finish any old drive */
574     finish_drive_clause();
575
576     /* purge out data of old configuration files */
577     purge(drive, file_nr);
578         
579     /* reserve slot */
580     append();
581     items = sscanf(token,"%c %s %i %i %i %i %li",
582                    &devices[cur_dev].drive,name,&devices[cur_dev].fat_bits,
583                    &devices[cur_dev].tracks,&devices[cur_dev].heads,
584                    &devices[cur_dev].sectors, &offset);
585     devices[cur_dev].offset = (off_t) offset;
586     switch(items){
587         case 2:
588             devices[cur_dev].fat_bits = 0;
589             /* fall thru */
590         case 3:
591             devices[cur_dev].sectors = 0;
592             devices[cur_dev].heads = 0;
593             devices[cur_dev].tracks = 0;
594             /* fall thru */
595         case 6:
596             devices[cur_dev].offset = 0;
597             /* fall thru */
598         default:
599             break;
600         case 0:
601         case 1:
602         case 4:
603         case 5:
604             syntax("bad number of parameters", 1);
605             exit(1);
606     }
607     if(!devices[cur_dev].tracks){
608         devices[cur_dev].sectors = 0;
609         devices[cur_dev].heads = 0;
610     }
611         
612     devices[cur_dev].drive = toupper(devices[cur_dev].drive);
613     maintain_default_drive(devices[cur_dev].drive);
614     if (!(devices[cur_dev].name = strdup(name))) {
615         printOom();
616         exit(1);
617     }
618     finish_drive_clause();
619     pos=0;
620 }
621
622 static int parse_one(int privilege)
623 {
624     int action=0;
625
626     get_next_token();
627     if(!token)
628         return 0;
629
630     if((match_token("drive") && ((action = 1)))||
631        (match_token("drive+") && ((action = 2))) ||
632        (match_token("+drive") && ((action = 3))) ||
633        (match_token("clear_drive") && ((action = 4))) ) {
634         /* finish off the previous drive */
635         finish_drive_clause();
636
637         get_next_token();
638         if(token_length != 1)
639             syntax("drive letter expected", 0);
640
641         if(action==1 || action==4)
642             /* replace existing drive */                        
643             purge(token[0], file_nr);
644         if(action==4)
645             return 1;
646         if(action==3)
647             prepend();
648         else
649             append();
650         memset((char*)(devices+cur_dev), 0, sizeof(*devices));
651         trusted = privilege;
652         flag_mask = 0;
653         devices[cur_dev].drive = toupper(token[0]);
654         maintain_default_drive(devices[cur_dev].drive);
655         expect_char(':');
656         return 1;
657     }
658     if(token_nr == 1 && token_length == 1) {
659         parse_old_device_line(token[0]);
660         return 1;
661     }
662
663     if((cur_dev < 0 ||
664         (set_var(dswitches,
665                  sizeof(dswitches)/sizeof(*dswitches),
666                  (caddr_t)&devices[cur_dev]) &&
667          set_openflags(&devices[cur_dev]) &&
668          set_misc_flags(&devices[cur_dev]) &&
669          set_def_format(&devices[cur_dev]))) &&
670        set_var(global_switches,
671                sizeof(global_switches)/sizeof(*global_switches), 0))
672         syntax("unrecognized keyword", 1);
673     return 1;
674 }
675
676 static int parse(const char *name, int privilege)
677 {
678     if(fp) {
679         fprintf(stderr, "File descriptor already set (%p)!\n", fp);
680         exit(1);
681     }
682     fp = fopen(name, "r");
683     if(!fp)
684         return 0;
685     file_nr++;
686     filename = name; /* no strdup needed: although lifetime of variable
687                         exceeds this function (due to dev->cfg_filename),
688                         we know that the name is always either
689                         1. a constant
690                         2. a statically allocate buffer
691                         3. an environment variable that stays unchanged
692                      */
693     linenumber = 0;
694     lastTokenLinenumber = 0;
695     pos = 0;
696     token = 0;
697     cur_dev = -1; /* no current device */
698
699     while(parse_one(privilege));
700     finish_drive_clause();
701     fclose(fp);
702     filename = NULL;
703     fp = NULL;
704     return 1;
705 }
706
707 void read_config(void)
708 {
709     char *homedir;
710     char *envConfFile;
711     static char conf_file[MAXPATHLEN+sizeof(CFG_FILE1)];
712
713         
714     /* copy compiled-in devices */
715     file_nr = 0;
716     cur_devs = nr_const_devices;
717     nr_dev = nr_const_devices + 2;
718     devices = NewArray(nr_dev, struct device);
719     if(!devices) {
720         printOom();
721         exit(1);
722     }
723     if(nr_const_devices)
724         memcpy(devices, const_devices,
725                nr_const_devices*sizeof(struct device));
726
727     (void) ((parse(CONF_FILE,1) |
728              parse(LOCAL_CONF_FILE,1) |
729              parse(SYS_CONF_FILE,1)) ||
730             (parse(OLD_CONF_FILE,1) |
731              parse(OLD_LOCAL_CONF_FILE,1)));
732     /* the old-name configuration files only get executed if none of the
733      * new-name config files were used */
734
735     homedir = get_homedir();
736     if ( homedir ){
737         strncpy(conf_file, homedir, MAXPATHLEN );
738         conf_file[MAXPATHLEN]='\0';
739         strcat( conf_file, CFG_FILE1);
740         parse(conf_file,0);
741     }
742     memset((char *)&devices[cur_devs],0,sizeof(struct device));
743
744     envConfFile = getenv("MTOOLSRC");
745     if(envConfFile)
746         parse(envConfFile,0);
747
748     /* environmental variables */
749     get_env_conf();
750     if(mtools_skip_check)
751         mtools_fat_compatibility=1;
752 }
753
754 void mtoolstest(int argc, char **argv, int type)
755 {
756     /* testing purposes only */
757     struct device *dev;
758     char drive='\0';
759
760     if(argc > 1 && argv[1][0] && argv[1][1] == ':') {
761         drive = toupper(argv[1][0]);
762     }
763
764     for (dev=devices; dev->name; dev++) {
765         if(drive && drive != dev->drive)
766             continue;
767         printf("drive %c:\n", dev->drive);
768         printf("\t#fn=%d mode=%d ",
769                dev->file_nr, dev->mode);
770         if(dev->cfg_filename)
771             printf("defined in %s\n", dev->cfg_filename);
772         else
773             printf("builtin\n");
774         printf("\tfile=\"%s\" fat_bits=%d \n",
775                dev->name,dev->fat_bits);
776         printf("\ttracks=%d heads=%d sectors=%d hidden=%d\n",
777                dev->tracks, dev->heads, dev->sectors, dev->hidden);
778         printf("\toffset=0x%lx\n", (long) dev->offset);
779         printf("\tpartition=%d\n", dev->partition);
780
781         if(dev->misc_flags)
782             printf("\t");
783
784         if(DO_SWAP(dev))
785             printf("swap ");
786         if(IS_SCSI(dev))
787             printf("scsi ");
788         if(IS_PRIVILEGED(dev))
789             printf("privileged");
790         if(IS_MFORMAT_ONLY(dev))
791             printf("mformat_only ");
792         if(SHOULD_USE_VOLD(dev))
793             printf("vold ");
794 #ifdef USE_XDF
795         if(SHOULD_USE_XDF(dev))
796             printf("use_xdf ");
797 #endif
798         if(dev->misc_flags)
799             printf("\n");
800
801         if(dev->mode)
802             printf("\t");
803 #ifdef O_SYNC
804         if(dev->mode & O_SYNC)
805             printf("sync ");
806 #endif
807 #ifdef O_NDELAY
808         if((dev->mode & O_NDELAY))
809             printf("nodelay ");
810 #endif
811 #ifdef O_EXCL
812         if((dev->mode & O_EXCL))
813             printf("exclusive ");
814 #endif
815         if(dev->mode)
816             printf("\n");
817
818         if(dev->precmd)
819             printf("\tprecmd=%s\n", dev->precmd);
820
821         printf("\n");
822     }
823
824     printf("mtools_fat_compatibility=%d\n",mtools_fat_compatibility);
825     printf("mtools_skip_check=%d\n",mtools_skip_check);
826     printf("mtools_lower_case=%d\n",mtools_ignore_short_case);
827
828     exit(0);
829 }