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 */
30static char buffer[MAX_LINE_LEN+1]; /* buffer for the whole line */
31static char *pos; /* position in line */
32static char *token; /* last scanned token */
33static size_t token_length; /* length of the token */
34static FILE *fp; /* file pointer for configuration file */
35static int linenumber; /* current line number. Only used for printing
36						* error messages */
37static int lastTokenLinenumber; /* line numnber for last token */
38static const char *filename=NULL; /* current file name. Used for printing
39				   * error messages, and for storing in
40				   * the device definition (mtoolstest) */
41static int file_nr=0;
42
43
44static int flag_mask; /* mask of currently set flags */
45
46/* devices */
47static int cur_devs; /* current number of defined devices */
48static int cur_dev; /* device being filled in. If negative, none */
49static int trusted=0; /* is the currently parsed device entry trusted? */
50static int nr_dev; /* number of devices that the current table can hold */
51struct device *devices; /* the device table */
52static int token_nr; /* number of tokens in line */
53
54static char default_drive='\0'; /* default drive */
55
56/* "environment" variables */
57unsigned int mtools_skip_check=1;       // Foxconn modified pling 05/07/2009, 0->1
58unsigned int mtools_fat_compatibility=0;
59unsigned int mtools_ignore_short_case=0;
60unsigned int mtools_rate_0=0;
61unsigned int mtools_rate_any=0;
62unsigned int mtools_no_vfat=0;
63unsigned int mtools_numeric_tail=1;
64unsigned int mtools_dotted_dir=0;
65unsigned int mtools_twenty_four_hour_clock=1;
66unsigned int mtools_default_codepage=850;
67const char *mtools_date_string="yyyy-mm-dd";
68char *country_string=0;
69
70typedef 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
80static 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
96typedef struct {
97    const char *name;
98    int flag;
99} flags_t;
100
101static 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
115static 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
129static 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
159static 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
176static 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
185char get_default_drive(void)
186{
187    if(default_drive != '\0')
188	return default_drive;
189    else
190	return 'A';
191}
192
193static 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
207static 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
225static 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
238static 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 */
256static 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
270static int match_token(const char *template)
271{
272    return (strlen(template) == token_length &&
273	    !strncasecmp(template, token, token_length));
274}
275
276static 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
288static 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
304static 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
319static 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 */
335static 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
348static 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
360static 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 */
367static 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 */
381static void append(void)
382{
383    grow();
384    cur_dev = cur_devs;
385    cur_devs++;
386    init_drive();
387}
388
389
390static 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
425static 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
447static 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
460static 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
486static 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
508static int parse_one(int privilege);
509
510/* check for offset embedded in file name, in the form file@@offset[SKMG] */
511static 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
541void 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
567static 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
622static 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
676static 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
707void 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
754void 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}
830