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