GetNumberFormatA implementation added.
[wine] / tools / wineconf
1 #!/usr/bin/perl -w
2
3 # This program generates wine.conf files on STDOUT.
4 # (C) 1996 Stephen Simmons
5 # Redistributable under Wine License
6
7 $RCS_ID = '$Id$ ';
8
9 # This program examines the contents of the DOS filesystems and
10 # attempts to generate a sensible wine.conf file.  This is output
11 # to STDOUT.
12 # It reads /etc/FSTAB to find mounting locations of the hard disk drives
13 # It uses the correct algorithm for ordering DOS drives, with the
14 # exception of the case of multiple drive controller types, where I don't
15 # know what DOS's algorithm is.
16 # It uses find to find all of the win.ini files on any DOS partition
17 # and sorts them by age to guess which is part of the active Windows
18 # installation.
19 # It reads the autoexec.bat file (if found) and records all variable
20 # settings.   There are some inaccuracies in its determination.
21 # First, while variables are interpolated properly, no control
22 # structures are supported so calls and execs to other batch files are
23 # ignored, and all variable settings take effect regardless of whether
24 # they would in DOS (i,e., both if and else clauses are read).
25 # This is used to determine the path and temp directories.  Duplicate
26 # path directories and path directories that don't exist are thrown
27 # out.
28 # On failing to find C:\AUTOEXEC.BAT, wineconf finds all executables
29 # in the windows directory and subdirectories, and generates an
30 # optimized path statement encompassing all the executables.
31 # Then it also looks for \TEMP and \TMP on all drives taking the first
32 # one it finds.
33 # wineconf doesn't support floppy drives, network drives, printers,
34 # and serial device configuration is hardcoded and not configured for
35 # the machine it runs on.  Similarly, spy parameters are hard coded.
36
37 # It would make sense to incorporate much of the hueristic code in
38 # this program into a library to be shared with a dosemu configuration
39 # program, because it seems that at least some of the same stuff will
40 # be wanted.  The program needs to be cleaned up still.  A better tmp
41 # search algorithm could be written.  A fast option is planned.  Less
42 # Linux-dependence is desired.  Should look for devices independent
43 # of /etc/fstab; then sanity checks on /etc/fstab can be performed.
44
45 use Getopt::Long;
46 use File::Basename;
47 use strict;
48 use Carp;
49
50 GetOptions('windir=s', 'sysdir=s', 'thorough', 'debug:s') || &Usage;
51
52 &ReadFSTAB();
53 &FindWindowsDir();
54 &ReadAutoexecBat();
55 &StandardStuff();
56
57 sub Usage {
58     print "Usage: $0 <options>\n";
59 #    print "-fstab <filename>    Location of alternate fstab file\n";
60     print "-windir <filename>   Location of windows dir in DOS space\n";
61     print "-thorough            Do careful analysis (default)\n";
62     print "-sysdir <filename>   Location of systems dir in DOS space\n";
63 #    print "-tmpdir <filename>   Location of tmp directory\n";
64     print "Generates (to STDOUT) a wine configuration file based on\n";
65     print "/etc/fstab and searching around in DOS directories\n";
66     print "The options above can override certain values\n";
67     print "This should be considered ALPHA code\n";
68     exit(0);
69 }
70
71 sub ReadFSTAB {
72     $::opt_f = $::opt_f ? $::opt_f : '/etc/fstab';
73     open(FSTAB, $::opt_f) || die "Cannot read $::opt_f\n";
74     while(<FSTAB>) {
75         next if /^\s*\#/;
76         next if /^\s*$/;
77
78         my ($device, $mntpoint, $type, @rest) = split(' ', $_);
79         if ($device !~ m"^/dev/fd") {
80             if ($type eq "msdos" || $type eq "vfat") {
81                 push(@::FatDrives, [$device, $mntpoint]);
82             }
83             elsif ($type eq "iso9660" || ($device eq '/dev/cdrom' && $type eq 'auto') ) {
84                 push(@::CdromDrives, [$device, $mntpoint]);
85             }
86         }
87     }
88     if (!@::FatDrives) {
89         warn "ERROR ($0): Cannot find any MSDOS drives.\n";
90         warn "This does not mean you cannot run Wine, but $0\n";
91         warn "cannot help you (yet)\n";
92         exit(1);
93     }
94     my $MagicDrive = 'C';
95     @::FatDrives = sort byDriveOrder @::FatDrives;
96     @::CdromDrives = sort byCdOrder @::CdromDrives;
97     foreach my $FatDrive (@::FatDrives) {
98         print "[Drive $MagicDrive]\n";
99         my $MntPoint = $FatDrive->[1];
100         print "Path=$MntPoint\n";
101         print "Type=hd\n";
102         print "\n";
103         &RegisterDrive($MagicDrive, $FatDrive);
104         if(!&IsMounted($FatDrive->[0])) {
105             warn "WARNING: DOS Drive $MagicDrive (" . $FatDrive->[0] . 
106                 ") is not mounted\n";
107         }
108         $MagicDrive++;
109     }
110     foreach my $CdromDrive (@::CdromDrives) {
111         print "[Drive $MagicDrive]\n";
112         my $MntPoint = $CdromDrive->[1];
113         print "Path=$MntPoint\n";
114         print "Type=cdrom\n";
115         print "\n";
116         &RegisterDrive($MagicDrive, $CdromDrive);
117         $MagicDrive++;
118     }
119 }
120
121 sub FindWindowsDir {
122     my($MagicDrive) = 'C';
123     my(@FATD)=@::FatDrives;
124     my(@wininis) = ();
125     my ($winini);
126
127     if (!$::opt_windir && !$::opt_fast && !$::opt_thorough) {
128         $::opt_thorough++;
129     }
130     if ($::opt_windir) {
131         $winini = &ToUnix($::opt_windir);
132         if (!-e $winini) {
133             die "ERROR: Specified winini file does not exist\n";
134         }
135     }
136     elsif ($::opt_fast) {
137         die "-fast code can be implemented\n";
138     }
139     elsif ($::opt_thorough) {
140         if ($::opt_debug) { print STDERR "DEBUG: Num FATD = ", $#FATD+1, "\n"; }
141         foreach(@FATD) {
142             my $ThisDrive = shift(@FATD);
143             my $MntPoint = $ThisDrive->[1];
144             push(@wininis, `find $MntPoint -name win.ini -print`);
145         }
146         foreach $winini (@wininis) {
147             chomp $winini;
148         }
149         my ($winini_cnt) = $#wininis+1;
150         if ($::opt_debug) { 
151             print STDERR "DEBUG: Num wininis found: $winini_cnt\n";}
152         if ($winini_cnt > 1) {
153             warn "$winini_cnt win.ini files found:\n";
154             @wininis = sort byFileAge @wininis;
155             warn join("\n", @wininis), "\n";
156             $winini = $wininis[0];
157             warn "Using most recent one: $winini\n";
158         }
159         elsif ($winini_cnt == 0) {
160             die "ERROR: No win.ini found in DOS partitions\n";
161         }
162         else {
163             $winini = $wininis[0];
164         }
165     }
166     else {
167         die "ERROR: None of -windir, -fast, or -thorough set\n";
168     }
169     $::windir = &ToDos(dirname($winini));
170     print "[wine]\n";
171     print "windows=$::windir\n";
172     if ($::opt_sysdir) {
173         print "system=$::opt_sysdir\n";
174     }
175     else {
176         print "system=$::windir\\SYSTEM\n";
177     }
178 }
179
180 # Returns 1 if the device is mounted; -1 if mount check failed; 0 if not
181 # mounted.
182 # This code is Linux specific, and needs to be broadened.
183 sub IsMounted {
184     my($Device) = @_;
185     if (-d "/proc") {
186         if (-e "/proc/mounts") {
187             open(MOUNTS, "/proc/mounts") || 
188                 (warn "Cannot open /proc/mounts, although it exists\n" &&
189                  return -1);
190             while(<MOUNTS>) {
191                 if (/^$Device/) { 
192                     return 1; # Tested 1.4
193                 }
194             }
195             return 0; # Tested 1.4
196         }
197     }
198     return -1;
199 }
200
201 sub RegisterDrive {
202     my($DOSdrive, $Drive) = @_;
203     $::DOS2Unix{$DOSdrive} = $Drive;
204     $::Device2DOS{$Drive->[0]} = $DOSdrive;
205     $::MntPoint2DOS{$Drive->[1]} = $DOSdrive;
206     $::DOS2MntPoint{$DOSdrive} = $Drive->[1];
207     $::DOS2Device{$DOSdrive} = $Drive->[0];
208 }
209
210 sub ReadAutoexecBat {
211     if (!%::DOS2Unix) { &ReadFSTAB; }
212     my($DriveC) = $::DOS2MntPoint{"C"};
213     $DriveC =~ s%/$%%;
214     my($path);
215     if ($::opt_debug) { 
216         print STDERR "DEBUG: Looking for $DriveC/autoexec.bat\n"; }
217     if (-e "$DriveC/autoexec.bat") {
218         # Tested 1.4
219         open(AUTOEXEC, "$DriveC/autoexec.bat") || 
220             die "Cannot read autoexec.bat\n";
221         while(<AUTOEXEC>) {
222             s/\015//;
223             if (/^\s*(set\s+)?(\w+)\s*[\s\=]\s*(.*)$/i) {
224                 my($varname) = $2;
225                 my($varvalue) = $3;
226                 chomp($varvalue);
227                 $varname =~ tr/A-Z/a-z/;
228                 while ($varvalue =~ /%(\w+)%/) {
229                     my $matchname = $1;
230                     my $subname = $1;
231                     $subname =~ tr/A-Z/a-z/;
232                     if ($::opt_debug =~ /path/i) {
233                         print STDERR "DEBUG: Found $matchname as $subname\n";
234                         print STDERR "DEBUG: Old varvalue:\n$varvalue\n";
235                         print STDERR "DEBUG: Old subname value:\n" .
236                             $::DOSenv{$subname} . "\n";
237                     }
238                     if ($::DOSenv{$subname}) {
239                         $varvalue =~ s/\%$matchname\%/$::DOSenv{$subname}/;
240                     }
241                     else {
242                         warn "DOS environment variable $subname not\n"; 
243                         warn "defined in autoexec.bat. (Reading config.sys\n";
244                         warn "is not implemented.)  Using null value\n";
245                         $varvalue =~ s/%$matchname%//;
246                     }
247                     if ($::opt_debug =~ /path/i) {
248                         print STDERR "DEBUG: New varvalue:\n$varvalue\n";
249                     }
250                 }
251                 if ($::opt_debug) {
252                     print STDERR "DEBUG: $varname = $varvalue\n";
253                 }
254                 $::DOSenv{$varname} = $varvalue;
255             }
256         }
257         close(AUTOEXEC);
258     }
259     else {
260         # Tested 1.4
261         warn "WARNING: C:\\AUTOEXEC.BAT was not found.\n";
262     }
263
264     if ($::DOSenv{"path"}) {
265         my @pathdirs = split(/\s*;\s*/, $::DOSenv{"path"});
266         if ($::opt_debug =~ /path/i) {
267             print STDERR "DEBUG (path): @pathdirs\n";
268         }
269         foreach my $pathdir (@pathdirs) {
270             if (-d &ToUnix($pathdir)) {
271                 if ($::DOSpathdir{$pathdir}++) {
272                     warn "Ignoring duplicate DOS path entry $pathdir\n";
273                 }
274                 else {
275                     if ($::opt_debug =~ /path/i) {
276                         print STDERR "DEBUG (path): Found $pathdir\n";
277                     }
278                     push(@::DOSpathlist, $pathdir);
279                 }
280             }
281             else {
282                 warn "Ignoring DOS path directory $pathdir, as it does not\n";
283                 warn "exist\n";
284             }
285         }
286         print "path=" . join(";", @::DOSpathlist) . "\n";
287     }
288     else {
289         # Code status: tested 1.4
290         warn "WARNING: Making assumptions for PATH\n";
291         warn "Will scan windows directory for executables and generate\n";
292         warn "path from that\n";
293         my $shellcmd = 'find ' . &ToUnix($::windir) . " -iregex '" .
294             '.*\.\(exe\|bat\|com\|dll\)' . "' -print";
295         if ($::opt_debug) { 
296             print STDERR "DEBUG: autoexec.bat search command:\n $shellcmd\n";
297         }
298         push(@::DOScommand, `$shellcmd`);
299         if ($::opt_debug && $::opt_debug =~ /autoexec/i) { 
300             print STDERR "DEBUG: autoexec.bat search results:\n\@DOS::command\n";
301         }
302         foreach my $command (@::DOScommand) {
303             $command =~ s%[^/]+$%%;
304             $::DOSexecdir{$command}++;
305         }
306         print "path=" . 
307             join(";", 
308                  grep(s%/$%%, 
309                       sort {$::DOSexecdir{$b} <=> $::DOSexecdir{$a}}
310                       (keys %::DOSexecdir))) . "\n";
311     }
312
313     if ($::DOSenv{"temp"} && -d &ToUnix($::DOSenv{"temp"})) {
314         print "temp=" . $::DOSenv{"temp"} . "\n";
315     }
316     else {
317         my $TheTemp;
318
319         warn "WARNING: Making assumptions for TEMP\n";
320         warn "Looking for \\TEMP and then \\TMP on every drive\n";
321         # Watch out .. might pick CDROM drive :-)
322         foreach my $DOSdrive (keys %::DOS2Unix) {
323             my $tmp = &ToUnix("$DOSdrive:\\temp");
324             if (-d $tmp) { $TheTemp = "$DOSdrive:\\temp"; last; }
325             $tmp = &ToUnix("$DOSdrive:\\tmp");
326             if (-d $tmp) { $TheTemp = "$DOSdrive:\\tmp"; last; }
327         }
328         $TheTemp = '/tmp' if (!$TheTemp && -d '/tmp');
329         if ($TheTemp) {
330             warn "Using $TheTemp\n";
331             print "temp=$TheTemp\n";
332         }
333         else {
334             warn "Using C:\\\n";
335             print "temp=C:\\\n";
336         }
337     }
338     print "\n";
339 }
340
341 # FNunix = &ToUnix(FNdos);
342 #   Converts DOS filenames to Unix filenames, leaving Unix filenames
343 #   untouched.
344 sub ToUnix {
345     my($FNdos) = @_;
346     my($FNunix);
347
348     # Initialize tables if necessary.
349     if (!%::DOS2Unix) { &ReadFSTAB; }
350
351     # Determine which type of conversion is necessary
352     if ($FNdos =~ /^([A-Z])\:(.*)$/) { # DOS drive specified
353         $FNunix = $::DOS2MntPoint{$1} . "/$2";
354     }
355     elsif ($FNdos =~ m%\\%) { # DOS drive not specified, C: is default
356         $FNunix = $::DOS2MntPoint{"C"} . "/$FNdos";
357     }
358     else { # Unix filename
359         $FNunix = $FNdos;
360     }
361     1 while ($FNunix =~ s%\\%/%);    # Convert \ to /
362     $FNunix =~ tr/A-Z/a-z/;          # Translate to lower case
363     1 while ($FNunix =~ s%//%/%);    # Translate double / to /
364     return $FNunix;
365 }
366
367 # FNdos = &ToDOS(FNunix)
368 #   Converts Unix filenames to DOS filenames
369 sub ToDos {
370     my($FNunix) = @_;
371     my(@MntList) = keys %::MntPoint2DOS;
372     my ($TheMntPt, $FNdos);
373
374     foreach my $MntPt (@MntList) { # Scan mount point list to see if path matches
375         if ($FNunix =~ /^$MntPt/) {
376             $TheMntPt = $MntPt;
377             last;
378         }
379     }
380     if (!$TheMntPt) {
381         Carp("ERROR: $FNunix not found in DOS directories\n");
382         exit(1);
383     }
384     $FNdos = $FNunix;
385     $FNdos =~ s/^$TheMntPt//;
386     $FNdos = $::MntPoint2DOS{$TheMntPt} . ":" . $FNdos;
387     1 while($FNdos =~ s%/%\\%);
388     return $FNdos;
389 }
390
391 sub InsertDefaultFile {
392     my ($fileName, $tag) = @_;
393     my $state = 0;
394
395     if (open(DEFFILE, "$fileName")) {
396        while (<DEFFILE>) {
397           $state = 0 if ($state == 1 && $_ =~ /^[ \t]*\#/o && index($_, "</$tag>") >= 0);
398           print $_ if ($state == 1);
399           $state = 1 if ($state == 0 && $_ =~ /^[ \t]*\#/o && index($_, "<$tag>" ) >= 0);
400        }
401        close(DEFFILE);
402     } else {
403        print STDERR "Cannot read $fileName\n";
404     }
405 }
406
407 sub StandardStuff {
408    &InsertDefaultFile("./wine.ini", "wineconf");
409 }
410
411 sub byFileAge {
412     -M $a <=> -M $b;
413 }
414
415 sub byDriveOrder {
416     my($DeviceA) = $a->[0];
417     my($DeviceB) = $b->[0];
418
419     # Primary drives come first, logical drives last
420     # DOS User's Guide (version 6) p. 70, IBM version.
421     # If both drives are the same type, sort alphabetically
422     # This makes drive a come before b, etc.
423     # It also makes SCSI drives come before IDE drives;
424     # this may or may not be right :-(
425     my($Alogical, $Blogical);
426     if (substr($DeviceA, 3, 1) >= 5) { $Alogical++; }
427     if (substr($DeviceB, 3, 1) >= 5) { $Blogical++; }
428     if ($Alogical && !$Blogical) { return -1; }
429     elsif ($Blogical && !$Alogical) { return 1; }
430     else { return ($DeviceA cmp $DeviceB); }
431 }
432
433 sub byCdOrder {
434     my($DeviceA) = $a->[0];
435     my($DeviceB) = $b->[0];
436     $DeviceA cmp $DeviceB;
437 }