Added sanity check for whitespace in file names, various other fixups.
[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 Carp;
48
49 GetOptions('windir=s', 'sysdir=s', 'thorough', 'debug:s') || &Usage;
50
51 &ReadFSTAB();
52 &FindWindowsDir();
53 &ReadAutoexecBat();
54 &StandardStuff();
55
56 sub Usage {
57     print "Usage: $0 <options>\n";
58 #    print "-fstab <filename>    Location of alternate fstab file\n";
59     print "-windir <filename>   Location of windows dir in DOS space\n";
60     print "-thorough            Do careful analysis (default)\n";
61     print "-sysdir <filename>   Location of systems dir in DOS space\n";
62 #    print "-tmpdir <filename>   Location of tmp directory\n";
63     print "Generates (to STDOUT) a wine configuration file based on\n";
64     print "/etc/fstab and searching around in DOS directories\n";
65     print "The options above can override certain values\n";
66     print "This should be considered ALPHA code\n";
67     exit(0);
68 }
69
70 sub ReadFSTAB {
71     $opt_f = $opt_f ? $opt_f : '/etc/fstab';
72     open(FSTAB, $opt_f) || die "Cannot read $opt_f\n";
73     while(<FSTAB>) {
74         next if /^\s*\#/;
75         next if /^\s*$/;
76         ($device, $mntpoint, $type, @rest) = split(' ', $_);
77         if ($device !~ m"^/dev/fd") {
78             if ($type eq "msdos" || $type eq "vfat") {
79                 push(@FatDrives, [$device, $mntpoint]);
80             }
81             elsif ($type eq "iso9660") {
82                 push(@CdromDrives, [$device, $mntpoint]);
83             }
84         }
85     }
86     if (!@FatDrives) {
87         warn "ERROR ($0): Cannot find any MSDOS drives.\n";
88         warn "This does not mean you cannot run Wine, but $0\n";
89         warn "cannot help you (yet)\n";
90         exit(1);
91     }
92     $MagicDrive = 'C';
93     @FatDrives = sort byDriveOrder @FatDrives;
94     @CdromDrives = sort byCdOrder @CdromDrives;
95     foreach $FatDrive (@FatDrives) {
96         print "[Drive $MagicDrive]\n";
97         $MntPoint = $FatDrive->[1];
98         print "Path=$MntPoint\n";
99         print "Type=hd\n";
100         print "\n";
101         &RegisterDrive($MagicDrive, $FatDrive);
102         if(!&IsMounted($FatDrive->[0])) {
103             warn "WARNING: DOS Drive $MagicDrive (" . $FatDrive->[0] . 
104                 ") is not mounted\n";
105         }
106         $MagicDrive++;
107     }
108     foreach $CdromDrive (@CdromDrives) {
109         print "[Drive $MagicDrive]\n";
110         $MntPoint = $CdromDrive->[1];
111         print "Path=$MntPoint\n";
112         print "Type=cdrom\n";
113         print "\n";
114         &RegisterDrive($MagicDrive, $CdromDrive);
115         $MagicDrive++;
116     }
117 }
118
119 sub FindWindowsDir {
120     my($MagicDrive) = 'C';
121     my(@FATD)=@FatDrives;
122     my(@wininis) = ();
123     if (!$opt_windir && !$opt_fast && !$opt_thorough) {
124         $opt_thorough++;
125     }
126     if ($opt_windir) {
127         $winini = &ToUnix($opt_windir);
128         if (!-e $winini) {
129             die "ERROR: Specified winini file does not exist\n";
130         }
131     }
132     elsif ($opt_fast) {
133         die "-fast code can be implemented\n";
134     }
135     elsif ($opt_thorough) {
136         if ($opt_debug) { print STDERR "DEBUG: Num FATD = ", $#FATD+1, "\n"; }
137         foreach(@FATD) {
138             $ThisDrive = shift(@FATD);
139             $MntPoint = $ThisDrive->[1];
140             push(@wininis, `find $MntPoint -name win.ini -print`);
141         }
142         foreach $winini (@wininis) {
143             chomp $winini;
144         }
145         my($winini_cnt) = $#wininis+1;
146         if ($opt_debug) { 
147             print STDERR "DEBUG: Num wininis found: $winini_cnt\n";}
148         if ($winini_cnt > 1) {
149             warn "$winini_cnt win.ini files found:\n";
150             @wininis = sort byFileAge @wininis;
151             warn join("\n", @wininis), "\n";
152             $winini = $wininis[0];
153             warn "Using most recent one: $winini\n";
154         }
155         elsif ($winini_cnt == 0) {
156             die "ERROR: No win.ini found in DOS partitions\n";
157         }
158         else {
159             $winini = $wininis[0];
160         }
161     }
162     else {
163         die "ERROR: None of -windir, -fast, or -thorough set\n";
164     }
165     $windir = &ToDos(dirname($winini));
166     print "[wine]\n";
167     print "windows=$windir\n";
168     if ($opt_sysdir) {
169         print "system=$opt_sysdir\n";
170     }
171     else {
172         print "system=$windir\\SYSTEM\n";
173     }
174 }
175
176 # Returns 1 if the device is mounted; -1 if mount check failed; 0 if not
177 # mounted.
178 # This code is Linux specific, and needs to be broadened.
179 sub IsMounted {
180     my($Device) = @_;
181     if (-d "/proc") {
182         if (-e "/proc/mounts") {
183             open(MOUNTS, "/proc/mounts") || 
184                 (warn "Cannot open /proc/mounts, although it exists\n" &&
185                  return -1);
186             while(<MOUNTS>) {
187                 if (/^$Device/) { 
188                     return 1; # Tested 1.4
189                 }
190             }
191             return 0; # Tested 1.4
192         }
193     }
194     return -1;
195 }
196
197 sub RegisterDrive {
198     my($DOSdrive, $Drive) = @_;
199     $DOS2Unix{$DOSdrive} = $Drive;
200     $Device2DOS{$Drive->[0]} = $DOSdrive;
201     $MntPoint2DOS{$Drive->[1]} = $DOSdrive;
202     $DOS2MntPoint{$DOSdrive} = $Drive->[1];
203     $DOS2Device{$DOSdrive} = $Drive->[0];
204 }
205
206 sub ReadAutoexecBat {
207     if (!%DOS2Unix) { &ReadFSTAB; }
208     my($DriveC) = $DOS2MntPoint{"C"};
209     $DriveC =~ s%/$%%;
210     my($path);
211     if ($opt_debug) { 
212         print STDERR "DEBUG: Looking for $DriveC/autoexec.bat\n"; }
213     if (-e "$DriveC/autoexec.bat") {
214         # Tested 1.4
215         open(AUTOEXEC, "$DriveC/autoexec.bat") || 
216             die "Cannot read autoexec.bat\n";
217         while(<AUTOEXEC>) {
218             s/\015//;
219             if (/^\s*(set\s+)?(\w+)\s*[\s\=]\s*(.*)$/i) {
220                 my($varname) = $2;
221                 my($varvalue) = $3;
222                 chomp($varvalue);
223                 $varname =~ tr/A-Z/a-z/;
224                 while ($varvalue =~ /%(\w+)%/) {
225                     $matchname = $subname = $1;
226                     $subname =~ tr/A-Z/a-z/;
227                     if ($opt_debug =~ /path/i) {
228                         print STDERR "DEBUG: Found $matchname as $subname\n";
229                         print STDERR "DEBUG: Old varvalue:\n$varvalue\n";
230                         print STDERR "DEBUG: Old subname value:\n" .
231                             $DOSenv{$subname} . "\n";
232                     }
233                     if ($DOSenv{$subname}) {
234                         $varvalue =~ s/\%$matchname\%/$DOSenv{$subname}/;
235                     }
236                     else {
237                         warn "DOS environment variable $subname not\n"; 
238                         warn "defined in autoexec.bat. (Reading config.sys\n";
239                         warn "is not implemented.)  Using null value\n";
240                         $varvalue =~ s/%$matchname%//;
241                     }
242                     if ($opt_debug =~ /path/i) {
243                         print STDERR "DEBUG: New varvalue:\n$varvalue\n";
244                     }
245                 }
246                 if ($opt_debug) {
247                     print STDERR "DEBUG: $varname = $varvalue\n";
248                 }
249                 $DOSenv{$varname} = $varvalue;
250             }
251         }
252         close(AUTOEXEC);
253     }
254     else {
255         # Tested 1.4
256         warn "WARNING: C:\\AUTOEXEC.BAT was not found.\n";
257     }
258
259     if ($DOSenv{"path"}) {
260         @pathdirs = split(/\s*;\s*/, $DOSenv{"path"});
261         if ($opt_debug =~ /path/i) {
262             print STDERR "DEBUG (path): @pathdirs\n";
263         }
264         foreach $pathdir (@pathdirs) {
265             if (-d &ToUnix($pathdir)) {
266                 if ($DOSpathdir{$pathdir}++) {
267                     warn "Ignoring duplicate DOS path entry $pathdir\n";
268                 }
269                 else {
270                     if ($opt_debug =~ /path/i) {
271                         print STDERR "DEBUG (path): Found $pathdir\n";
272                     }
273                     push(@DOSpathlist, $pathdir);
274                 }
275             }
276             else {
277                 warn "Ignoring DOS path directory $pathdir, as it does not\n";
278                 warn "exist\n";
279             }
280         }
281         print "path=" . join(";", @DOSpathlist) . "\n";
282     }
283     else {
284         # Code status: tested 1.4
285         warn "WARNING: Making assumptions for PATH\n";
286         warn "Will scan windows directory for executables and generate\n";
287         warn "path from that\n";
288         $shellcmd = 'find ' . &ToUnix($windir) . " -iregex '" .
289             '.*\.\(exe\|bat\|com\|dll\)' . "' -print";
290         if ($opt_debug) { 
291             print STDERR "DEBUG: autoexec.bat search command:\n $shellcmd\n";
292         }
293         push(@DOScommand, `$shellcmd`);
294         if ($opt_debug =~ /autoexec/i) { 
295             print STDERR "DEBUG: autoexec.bat search results:\n@DOScommand\n";
296         }
297         foreach $command (@DOScommand) {
298             $command =~ s%[^/]+$%%;
299             $DOSexecdir{$command}++;
300         }
301         print "path=" . 
302             join(";", 
303                  grep(s%/$%%, 
304                       sort {$DOSexecdir{$b} <=> $DOSexecdir{$a}}
305                       (keys %DOSexecdir))) . "\n";
306     }
307
308     if ($DOSenv{"temp"} && -d &ToUnix($DOSenv{"temp"})) {
309         print "temp=" . $DOSenv{"temp"} . "\n";
310     }
311     else {
312         warn "WARNING: Making assumptions for TEMP\n";
313         warn "Looking for \\TEMP and then \\TMP on every drive\n";
314         # Watch out .. might pick CDROM drive :-)
315         foreach $DOSdrive (keys %DOS2Unix) {
316             $tmp = &ToUnix("$DOSdrive:\\temp");
317             if (-d $tmp) { $TheTemp = "$DOSdrive:\\temp"; last; }
318             $tmp = &ToUnix("$DOSdrive:\\tmp");
319             if (-d $tmp) { $TheTemp = "$DOSdrive:\\tmp"; last; }
320         }
321         if ($TheTemp) {
322             warn "Using $TheTemp\n";
323             print "temp=$TheTemp\n";
324         }
325         else {
326             warn "Using C:\\\n";
327             print "temp=C:\\\n";
328         }
329     }
330     print "\n";
331 }
332
333 # FNunix = &ToUnix(FNdos);
334 #   Converts DOS filenames to Unix filenames, leaving Unix filenames
335 #   untouched.
336 sub ToUnix {
337     my($FNdos) = @_;
338     my($FNunix);
339
340     # Initialize tables if necessary.
341     if (!%DOS2Unix) { &ReadFSTAB; }
342
343     # Determine which type of conversion is necessary
344     if ($FNdos =~ /^([A-Z])\:(.*)$/) { # DOS drive specified
345         $FNunix = $DOS2MntPoint{$1} . "/$2";
346     }
347     elsif ($FNdos =~ m%\\%) { # DOS drive not specified, C: is default
348         $FNunix = $DOS2MntPoint{"C"} . "/$FNdos";
349     }
350     else { # Unix filename
351         $FNunix = $FNdos;
352     }
353     1 while ($FNunix =~ s%\\%/%);    # Convert \ to /
354     $FNunix =~ tr/A-Z/a-z/;          # Translate to lower case
355     1 while ($FNunix =~ s%//%/%);    # Translate double / to /
356     return $FNunix;
357 }
358
359 # FNdos = &ToDOS(FNunix)
360 #   Converts Unix filenames to DOS filenames
361 sub ToDos {
362     my($FNunix) = @_;
363     my(@MntList) = keys %MntPoint2DOS;
364     foreach $MntPt (@MntList) { # Scan mount point list to see if path matches
365         if ($FNunix =~ /^$MntPt/) {
366             $TheMntPt = $MntPt;
367             last;
368         }
369     }
370     if (!$TheMntPt) {
371         Carp("ERROR: $FNunix not found in DOS directories\n");
372         exit(1);
373     }
374     $FNdos = $FNunix;
375     $FNdos =~ s/^$TheMntPt//;
376     $FNdos = $MntPoint2DOS{$TheMntPt} . ":" . $FNdos;
377     1 while($FNdos =~ s%/%\\%);
378     return $FNdos;
379 }
380
381
382 sub StandardStuff {
383     print "[serial]\n";
384     print "com1=/dev/cua0\n";
385     print "com2=/dev/cua1\n";
386     print "\n";
387     print "[spy]\n";
388     print ";File=CON\n";
389     print ";File=spy.log\n";
390     print "Exclude=WM_TIMER;WM_SETCURSOR;WM_MOUSEMOVE;WM_NCHITTEST;\n";
391     print "Include=WM_COMMAND;\n";
392 }
393
394 sub byFileAge {
395     -M $a <=> -M $b;
396 }
397
398 sub byDriveOrder {
399     my($DeviceA) = $a->[0];
400     my($DeviceB) = $b->[0];
401
402     # Primary drives come first, logical drives last
403     # DOS User's Guide (version 6) p. 70, IBM version.
404     # If both drives are the same type, sort alphabetically
405     # This makes drive a come before b, etc.
406     # It also makes SCSI drives come before IDE drives;
407     # this may or may not be right :-(
408     my($Alogical, $Blogical);
409     if (substr($DeviceA, 3, 1) >= 5) { $Alogical++; }
410     if (substr($DeviceB, 3, 1) >= 5) { $Blogical++; }
411     if ($Alogical && !$Blogical) { return -1; }
412     elsif ($Blogical && !$Alogical) { return 1; }
413     else { return ($DeviceA cmp $DeviceB); }
414 }
415
416 sub byCdOrder {
417     my($DeviceA) = $a->[0];
418     my($DeviceB) = $b->[0];
419     $DeviceA cmp $DeviceB;
420 }