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