/* Copyright 1996-2005,2007-2009,2011 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 . * */ #include "sysincludes.h" #include "mtools.h" #include "codepage.h" #include "mtoolsPaths.h" /* global variables */ /* they are not really harmful here, because there is only one configuration * file per invocations */ #define MAX_LINE_LEN 256 /* scanner */ static char buffer[MAX_LINE_LEN+1]; /* buffer for the whole line */ static char *pos; /* position in line */ static char *token; /* last scanned token */ static size_t token_length; /* length of the token */ static FILE *fp; /* file pointer for configuration file */ static int linenumber; /* current line number. Only used for printing * error messages */ static int lastTokenLinenumber; /* line numnber for last token */ static const char *filename=NULL; /* current file name. Used for printing * error messages, and for storing in * the device definition (mtoolstest) */ static int file_nr=0; static unsigned int flag_mask; /* mask of currently set flags */ /* devices */ static unsigned int cur_devs; /* current number of defined devices */ static int cur_dev; /* device being filled in. If negative, none */ static int trusted=0; /* is the currently parsed device entry trusted? */ static unsigned int nr_dev; /* number of devices that the current table can hold */ struct device *devices; /* the device table */ static int token_nr; /* number of tokens in line */ static char default_drive='\0'; /* default drive */ /* "environment" variables */ unsigned int mtools_skip_check=0; unsigned int mtools_fat_compatibility=0; unsigned int mtools_ignore_short_case=0; uint8_t mtools_rate_0=0; uint8_t mtools_rate_any=0; unsigned int mtools_no_vfat=0; unsigned int mtools_numeric_tail=1; unsigned int mtools_dotted_dir=0; unsigned int mtools_twenty_four_hour_clock=1; unsigned int mtools_lock_timeout=30; unsigned int mtools_default_codepage=850; const char *mtools_date_string="yyyy-mm-dd"; char *country_string=0; typedef struct switches_l { const char *name; caddr_t address; enum { T_INT, T_STRING, T_UINT, T_UINT8, T_UINT16 } type; } switches_t; static switches_t global_switches[] = { { "MTOOLS_LOWER_CASE", (caddr_t) & mtools_ignore_short_case, T_UINT }, { "MTOOLS_FAT_COMPATIBILITY", (caddr_t) & mtools_fat_compatibility, T_UINT }, { "MTOOLS_SKIP_CHECK", (caddr_t) & mtools_skip_check, T_UINT }, { "MTOOLS_NO_VFAT", (caddr_t) & mtools_no_vfat, T_UINT }, { "MTOOLS_RATE_0", (caddr_t) &mtools_rate_0, T_UINT8 }, { "MTOOLS_RATE_ANY", (caddr_t) &mtools_rate_any, T_UINT8 }, { "MTOOLS_NAME_NUMERIC_TAIL", (caddr_t) &mtools_numeric_tail, T_UINT }, { "MTOOLS_DOTTED_DIR", (caddr_t) &mtools_dotted_dir, T_UINT }, { "MTOOLS_TWENTY_FOUR_HOUR_CLOCK", (caddr_t) &mtools_twenty_four_hour_clock, T_UINT }, { "MTOOLS_DATE_STRING", (caddr_t) &mtools_date_string, T_STRING }, { "MTOOLS_LOCK_TIMEOUT", (caddr_t) &mtools_lock_timeout, T_UINT }, { "DEFAULT_CODEPAGE", (caddr_t) &mtools_default_codepage, T_UINT } }; typedef struct { const char *name; unsigned int flag; } flags_t; static flags_t openflags[] = { #ifdef O_SYNC { "sync", O_SYNC }, #endif #ifdef O_NDELAY { "nodelay", O_NDELAY }, #endif #ifdef O_EXCL { "exclusive", O_EXCL }, #endif { "none", 0 } /* hack for those compilers that choke on commas * after the last element of an array */ }; static flags_t misc_flags[] = { #ifdef USE_XDF { "use_xdf", USE_XDF_FLAG }, #endif { "scsi", SCSI_FLAG }, { "nolock", NOLOCK_FLAG }, { "mformat_only", MFORMAT_ONLY_FLAG }, { "filter", FILTER_FLAG }, { "privileged", PRIV_FLAG }, { "vold", VOLD_FLAG }, { "remote", FLOPPYD_FLAG }, { "swap", SWAP_FLAG }, }; static struct { const char *name; signed char fat_bits; unsigned int tracks; unsigned short heads; unsigned short sectors; } default_formats[] = { { "hd514", 12, 80, 2, 15 }, { "high-density-5-1/4", 12, 80, 2, 15 }, { "1.2m", 12, 80, 2, 15 }, { "hd312", 12, 80, 2, 18 }, { "high-density-3-1/2", 12, 80, 2, 18 }, { "1.44m", 12, 80, 2, 18 }, { "dd312", 12, 80, 2, 9 }, { "double-density-3-1/2", 12, 80, 2, 9 }, { "720k", 12, 80, 2, 9 }, { "dd514", 12, 40, 2, 9 }, { "double-density-5-1/4", 12, 40, 2, 9 }, { "360k", 12, 40, 2, 9 }, { "320k", 12, 40, 2, 8 }, { "180k", 12, 40, 1, 9 }, { "160k", 12, 40, 1, 8 } }; #define OFFS(x) ((caddr_t)&((struct device *)0)->x) static switches_t dswitches[]= { { "FILE", OFFS(name), T_STRING }, { "OFFSET", OFFS(offset), T_UINT }, { "PARTITION", OFFS(partition), T_UINT }, { "FAT", OFFS(fat_bits), T_INT }, { "FAT_BITS", OFFS(fat_bits), T_UINT }, { "MODE", OFFS(mode), T_UINT }, { "TRACKS", OFFS(tracks), T_UINT }, { "CYLINDERS", OFFS(tracks), T_UINT }, { "HEADS", OFFS(heads), T_UINT16 }, { "SECTORS", OFFS(sectors), T_UINT16 }, { "HIDDEN", OFFS(hidden), T_UINT }, { "PRECMD", OFFS(precmd), T_STRING }, { "BLOCKSIZE", OFFS(blocksize), T_UINT }, { "CODEPAGE", OFFS(codepage), T_UINT } }; #if (defined HAVE_TOUPPER_L || defined HAVE_STRNCASECMP_L) static locale_t C=NULL; static void init_canon(void) { if(C == NULL) C = newlocale(LC_CTYPE_MASK, "C", NULL); } #endif #ifdef HAVE_TOUPPER_L static int canon_drv(int drive) { int ret; init_canon(); ret = toupper_l(drive, C); return ret; } #else static int canon_drv(int drive) { return toupper(drive); } #endif #ifdef HAVE_STRNCASECMP_L static int cmp_tok(const char *a, const char *b, int len) { init_canon(); return strncasecmp_l(a, b, len, C); } #else static int cmp_tok(const char *a, const char *b, int len) { return strncasecmp(a, b, len); } #endif static char ch_canon_drv(char drive) { return (char) canon_drv( (unsigned char) drive); } static void maintain_default_drive(char drive) { if(default_drive == ':') return; /* we have an image */ if(default_drive == '\0' || default_drive > drive) default_drive = drive; } char get_default_drive(void) { if(default_drive != '\0') return default_drive; else return 'A'; } static void syntax(const char *msg, int thisLine) NORETURN; static void syntax(const char *msg, int thisLine) { char drive='\0'; if(thisLine) lastTokenLinenumber = linenumber; if(cur_dev >= 0) drive = devices[cur_dev].drive; fprintf(stderr,"Syntax error at line %d ", lastTokenLinenumber); if(drive) fprintf(stderr, "for drive %c: ", drive); if(token) fprintf(stderr, "column %ld ", (long)(token - buffer)); fprintf(stderr, "in file %s: %s", filename, msg); if(errno != 0) fprintf(stderr, " (%s)", strerror(errno)); fprintf(stderr, "\n"); exit(1); } static void get_env_conf(void) { char *s; unsigned int i; for(i=0; i< sizeof(global_switches) / sizeof(*global_switches); i++) { s = getenv(global_switches[i].name); if(s) { errno = 0; switch(global_switches[i].type) { case T_INT: * ((int *)global_switches[i].address) = strtoi(s,0,0); break; case T_UINT: * ((unsigned int *)global_switches[i].address) = strtoui(s,0,0); break; case T_UINT8: * ((uint8_t *)global_switches[i].address) = strtou8(s,0,0); break; case T_UINT16: * ((uint16_t *)global_switches[i].address) = strtou16(s,0,0); break; case T_STRING: * ((char **)global_switches[i].address) = s; break; } if(errno != 0) { fprintf(stderr, "Bad number %s for %s (%s)\n", s, global_switches[i].name, strerror(errno)); exit(1); } } } } static int mtools_getline(void) { if(!fp || !fgets(buffer, MAX_LINE_LEN+1, fp)) return -1; linenumber++; pos = buffer; token_nr = 0; buffer[MAX_LINE_LEN] = '\0'; if(strlen(buffer) == MAX_LINE_LEN) syntax("line too long", 1); return 0; } static void skip_junk(int expect) { lastTokenLinenumber = linenumber; while(!pos || !*pos || strchr(" #\n\t", *pos)) { if(!pos || !*pos || *pos == '#') { if(mtools_getline()) { pos = 0; if(expect) syntax("end of file unexpected", 1); return; } } else pos++; } token_nr++; } /* get the next token */ static char *get_next_token(void) { skip_junk(0); if(!pos) { token_length = 0; token = 0; return 0; } token = pos; token_length = strcspn(token, " \t\n#:="); pos += token_length; return token; } static int match_token(const char *template) { return (strlen(template) == token_length && !cmp_tok(template, token, token_length)); } static void expect_char(char c) { char buf[11]; skip_junk(1); if(*pos != c) { sprintf(buf, "expected %c", c); syntax(buf, 1); } pos++; } static char *get_string(void) { char *end, *str; skip_junk(1); if(*pos != '"') syntax(" \" expected", 0); str = pos+1; end = strchr(str, '\"'); if(!end) syntax("unterminated string constant", 1); *end = '\0'; pos = end+1; return str; } static unsigned long get_unumber(unsigned long max) { char *last; unsigned long n; skip_junk(1); last = pos; n=strtoul(pos, &pos, 0); if(errno) syntax("bad number", 0); if(last == pos) syntax("numeral expected", 0); if(n > max) syntax("number too big", 0); pos++; token_nr++; return n; } static int get_number(void) { char *last; int n; skip_junk(1); last = pos; n=(int) strtol(pos, &pos, 0); if(errno) syntax("bad number", 0); if(last == pos) syntax("numeral expected", 0); pos++; token_nr++; return n; } /* purge all entries pertaining to a given drive from the table */ static void purge(char drive, int fn) { unsigned int i, j; drive = ch_canon_drv(drive); for(j=0, i=0; i < cur_devs; i++) { if(devices[i].drive != drive || devices[i].file_nr == fn) devices[j++] = devices[i]; } cur_devs = j; } static void grow(void) { if(cur_devs >= nr_dev - 2) { nr_dev = (cur_devs + 2) << 1; if(!(devices=Grow(devices, nr_dev, struct device))){ printOom(); exit(1); } } } static void init_drive(void) { memset((char *)&devices[cur_dev], 0, sizeof(struct device)); devices[cur_dev].ssize = 2; } /* prepends a device to the table */ static void prepend(void) { unsigned int i; grow(); for(i=cur_devs; i>0; i--) devices[i] = devices[i-1]; cur_dev = 0; cur_devs++; init_drive(); } /* appends a device to the table */ static void append(void) { grow(); cur_dev = cur_devs; cur_devs++; init_drive(); } static void finish_drive_clause(void) { if(cur_dev == -1) { trusted = 0; return; } if(!devices[cur_dev].name) syntax("missing filename", 0); if(devices[cur_dev].tracks || devices[cur_dev].heads || devices[cur_dev].sectors) { if(!devices[cur_dev].tracks || !devices[cur_dev].heads || !devices[cur_dev].sectors) syntax("incomplete geometry: either indicate all of track/heads/sectors or none of them", 0); if(!(devices[cur_dev].misc_flags & (MFORMAT_ONLY_FLAG | FILTER_FLAG))) syntax("if you supply a geometry, you also must supply one of the `mformat_only' or `filter' flags", 0); } devices[cur_dev].file_nr = file_nr; devices[cur_dev].cfg_filename = filename; if(! (flag_mask & PRIV_FLAG) && IS_SCSI(&devices[cur_dev])) devices[cur_dev].misc_flags |= PRIV_FLAG; if(!trusted && (devices[cur_dev].misc_flags & PRIV_FLAG)) { fprintf(stderr, "Warning: privileged flag ignored for drive %c: defined in file %s\n", canon_drv(devices[cur_dev].drive), filename); devices[cur_dev].misc_flags &= ~PRIV_FLAG; } trusted = 0; cur_dev = -1; } static int set_var(struct switches_l *switches, int nr, caddr_t base_address) { int i; for(i=0; i < nr; i++) { if(match_token(switches[i].name)) { expect_char('='); if(switches[i].type == T_UINT) * ((unsigned int *)((long)switches[i].address+base_address)) = (unsigned int) get_unumber(UINT_MAX); else if(switches[i].type == T_UINT8) * ((uint8_t *)((long)switches[i].address+base_address)) = (uint8_t) get_unumber(UINT8_MAX); else if(switches[i].type == T_UINT16) * ((uint16_t *)((long)switches[i].address+base_address)) = (uint16_t) get_unumber(UINT16_MAX); else if(switches[i].type == T_INT) * ((int *)((long)switches[i].address+base_address)) = get_number(); else if (switches[i].type == T_STRING) * ((char**)((long)switches[i].address+base_address))= strdup(get_string()); return 0; } } return 1; } static int set_openflags(struct device *dev) { unsigned int i; for(i=0; i < sizeof(openflags) / sizeof(*openflags); i++) { if(match_token(openflags[i].name)) { dev->mode |= openflags[i].flag; return 0; } } return 1; } static int set_misc_flags(struct device *dev) { unsigned int i; for(i=0; i < sizeof(misc_flags) / sizeof(*misc_flags); i++) { if(match_token(misc_flags[i].name)) { flag_mask |= misc_flags[i].flag; skip_junk(0); if(pos && *pos == '=') { pos++; switch(get_number()) { case 0: return 0; case 1: break; default: syntax("expected 0 or 1", 0); } } dev->misc_flags |= misc_flags[i].flag; return 0; } } return 1; } static int set_def_format(struct device *dev) { unsigned int i; for(i=0; i < sizeof(default_formats)/sizeof(*default_formats); i++) { if(match_token(default_formats[i].name)) { if(!dev->ssize) dev->ssize = 2; if(!dev->tracks) dev->tracks = default_formats[i].tracks; if(!dev->heads) dev->heads = default_formats[i].heads; if(!dev->sectors) dev->sectors = default_formats[i].sectors; if(!dev->fat_bits) dev->fat_bits = default_formats[i].fat_bits; return 0; } } return 1; } static void parse_all(int privilege); void set_cmd_line_image(char *img) { char *ofsp; prepend(); devices[cur_dev].drive = ':'; default_drive = ':'; ofsp = strstr(img, "@@"); if (ofsp == NULL) { /* no separator => no offset */ devices[cur_dev].name = strdup(img); devices[cur_dev].offset = 0; } else { devices[cur_dev].name = strndup(img, ofsp - img); devices[cur_dev].offset = str_to_offset(ofsp+2); } devices[cur_dev].fat_bits = 0; devices[cur_dev].tracks = 0; devices[cur_dev].heads = 0; devices[cur_dev].sectors = 0; if (strchr(devices[cur_dev].name, '|')) { char *pipechar = strchr(devices[cur_dev].name, '|'); *pipechar = 0; strncpy(buffer, pipechar+1, MAX_LINE_LEN); buffer[MAX_LINE_LEN] = '\0'; fp = NULL; filename = "{command line}"; linenumber = 0; lastTokenLinenumber = 0; pos = buffer; token = 0; parse_all(0); } } void check_number_parse_errno(char c, const char *oarg, char *endptr) { if(endptr && *endptr) { fprintf(stderr, "Bad number %s\n", oarg); exit(1); } if(errno) { fprintf(stderr, "Bad number %s for -%c (%s)\n", oarg, c, strerror(errno)); exit(1); } } static uint16_t tou16(int in, const char *comment) { if(in > UINT16_MAX) { fprintf(stderr, "Number of %s %d too big\n", comment, in); exit(1); } if(in < 0) { fprintf(stderr, "Number of %s %d negative\n", comment, in); exit(1); } return (uint16_t) in; } static void parse_old_device_line(char drive) { char name[MAXPATHLEN]; int items; long offset; int heads, sectors; /* finish any old drive */ finish_drive_clause(); /* purge out data of old configuration files */ purge(drive, file_nr); /* reserve slot */ append(); items = sscanf(token,"%c %s %i %i %i %i %li", &devices[cur_dev].drive,name,&devices[cur_dev].fat_bits, &devices[cur_dev].tracks,&heads, §ors, &offset); devices[cur_dev].heads = tou16(heads, "heads"); devices[cur_dev].sectors = tou16(sectors, "sectors"); devices[cur_dev].offset = (off_t) offset; switch(items){ case 2: devices[cur_dev].fat_bits = 0; /* fall thru */ case 3: devices[cur_dev].sectors = 0; devices[cur_dev].heads = 0; devices[cur_dev].tracks = 0; /* fall thru */ case 6: devices[cur_dev].offset = 0; /* fall thru */ default: break; case 0: case 1: case 4: case 5: syntax("bad number of parameters", 1); } if(!devices[cur_dev].tracks){ devices[cur_dev].sectors = 0; devices[cur_dev].heads = 0; } devices[cur_dev].drive = ch_canon_drv(devices[cur_dev].drive); maintain_default_drive(devices[cur_dev].drive); if (!(devices[cur_dev].name = strdup(name))) { printOom(); exit(1); } devices[cur_dev].misc_flags |= MFORMAT_ONLY_FLAG; finish_drive_clause(); pos=0; } static int parse_one(int privilege) { int action=0; get_next_token(); if(!token) return 0; if((match_token("drive") && ((action = 1)))|| (match_token("drive+") && ((action = 2))) || (match_token("+drive") && ((action = 3))) || (match_token("clear_drive") && ((action = 4))) ) { /* finish off the previous drive */ finish_drive_clause(); get_next_token(); if(token_length != 1) syntax("drive letter expected", 0); if(action==1 || action==4) /* replace existing drive */ purge(token[0], file_nr); if(action==4) return 1; if(action==3) prepend(); else append(); memset((char*)(devices+cur_dev), 0, sizeof(*devices)); trusted = privilege; flag_mask = 0; devices[cur_dev].drive = ch_canon_drv(token[0]); maintain_default_drive(devices[cur_dev].drive); expect_char(':'); return 1; } if(token_nr == 1 && token_length == 1) { parse_old_device_line(token[0]); return 1; } if((cur_dev < 0 || (set_var(dswitches, sizeof(dswitches)/sizeof(*dswitches), (caddr_t)&devices[cur_dev]) && set_openflags(&devices[cur_dev]) && set_misc_flags(&devices[cur_dev]) && set_def_format(&devices[cur_dev]))) && set_var(global_switches, sizeof(global_switches)/sizeof(*global_switches), 0)) syntax("unrecognized keyword", 1); return 1; } static void parse_all(int privilege) { errno=0; while (parse_one(privilege)); } static int parse(const char *name, int privilege) { if(fp) { fprintf(stderr, "File descriptor already set!\n"); exit(1); } fp = fopen(name, "r"); if(!fp) return 0; file_nr++; filename = name; /* no strdup needed: although lifetime of variable exceeds this function (due to dev->cfg_filename), we know that the name is always either 1. a constant 2. a statically allocate buffer 3. an environment variable that stays unchanged */ linenumber = 0; lastTokenLinenumber = 0; pos = 0; token = 0; cur_dev = -1; /* no current device */ parse_all(privilege); finish_drive_clause(); fclose(fp); filename = NULL; fp = NULL; return 1; } void read_config(void) { char *homedir; char *envConfFile; static char conf_file[MAXPATHLEN+sizeof(CFG_FILE1)]; /* copy compiled-in devices */ file_nr = 0; cur_devs = nr_const_devices; nr_dev = nr_const_devices + 2; devices = NewArray(nr_dev, struct device); if(!devices) { printOom(); exit(1); } if(nr_const_devices) memcpy(devices, const_devices, nr_const_devices*sizeof(struct device)); (void) ((parse(CONF_FILE,1) | parse(LOCAL_CONF_FILE,1) | parse(SYS_CONF_FILE,1)) || (parse(OLD_CONF_FILE,1) | parse(OLD_LOCAL_CONF_FILE,1))); /* the old-name configuration files only get executed if none of the * new-name config files were used */ homedir = get_homedir(); if ( homedir ){ strncpy(conf_file, homedir, MAXPATHLEN ); conf_file[MAXPATHLEN]='\0'; strcat( conf_file, CFG_FILE1); parse(conf_file,0); } memset((char *)&devices[cur_devs],0,sizeof(struct device)); envConfFile = getenv("MTOOLSRC"); if(envConfFile) parse(envConfFile,0); /* environmental variables */ get_env_conf(); if(mtools_skip_check) mtools_fat_compatibility=1; } void mtoolstest(int argc, char **argv, int type UNUSEDP) NORETURN; void mtoolstest(int argc, char **argv, int type UNUSEDP) { /* testing purposes only */ struct device *dev; char drive='\0'; if(argc > 1 && argv[1][0] && argv[1][1] == ':') { drive = ch_canon_drv(argv[1][0]); } for (dev=devices; dev->name; dev++) { if(drive && drive != dev->drive) continue; printf("drive %c:\n", dev->drive); printf("\t#fn=%d mode=%d ", dev->file_nr, dev->mode); if(dev->cfg_filename) printf("defined in %s\n", dev->cfg_filename); else printf("builtin\n"); printf("\tfile=\"%s\" fat_bits=%d \n", dev->name,dev->fat_bits); printf("\ttracks=%d heads=%d sectors=%d hidden=%d\n", dev->tracks, dev->heads, dev->sectors, dev->hidden); printf("\toffset=0x%lx\n", (long) dev->offset); printf("\tpartition=%d\n", dev->partition); if(dev->misc_flags) printf("\t"); if(DO_SWAP(dev)) printf("swap "); if(IS_SCSI(dev)) printf("scsi "); if(IS_PRIVILEGED(dev)) printf("privileged"); if(IS_MFORMAT_ONLY(dev)) printf("mformat_only "); if(SHOULD_USE_VOLD(dev)) printf("vold "); #ifdef USE_XDF if(SHOULD_USE_XDF(dev)) printf("use_xdf "); #endif if(dev->misc_flags) printf("\n"); if(dev->mode) printf("\t"); #ifdef O_SYNC if(dev->mode & O_SYNC) printf("sync "); #endif #ifdef O_NDELAY if((dev->mode & O_NDELAY)) printf("nodelay "); #endif #ifdef O_EXCL if((dev->mode & O_EXCL)) printf("exclusive "); #endif if(dev->mode) printf("\n"); if(dev->precmd) printf("\tprecmd=%s\n", dev->precmd); printf("\n"); } printf("mtools_fat_compatibility=%d\n",mtools_fat_compatibility); printf("mtools_skip_check=%d\n",mtools_skip_check); printf("mtools_lower_case=%d\n",mtools_ignore_short_case); exit(0); } /* * Local Variables: * c-basic-offset: 4 * End: */