#!/usr/bin/perl -w # This program generates wine.conf files on STDOUT. # Copyright (C) 1996 Stephen Simmons # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # NOTES: # # This program examines the contents of the DOS filesystems and # attempts to generate a sensible wine.conf file. This is output # to STDOUT. # It reads /etc/fstab to find mounting locations of the hard disk drives # It uses the correct algorithm for ordering DOS drives, with the # exception of the case of multiple drive controller types, where I don't # know what DOS's algorithm is. # It uses find to find all of the win.ini files on any DOS partition # and sorts them by age to guess which is part of the active Windows # installation. # It reads the autoexec.bat file (if found) and records all variable # settings. There are some inaccuracies in its determination. # First, while variables are interpolated properly, no control # structures are supported so calls and execs to other batch files are # ignored, and all variable settings take effect regardless of whether # they would in DOS (i,e., both if and else clauses are read). # This is used to determine the path and temp directories. Duplicate # path directories and path directories that don't exist are thrown # out. # On failing to find C:\AUTOEXEC.BAT, wineconf finds all executables # in the windows directory and subdirectories, and generates an # optimized path statement encompassing all the executables. # Then it also looks for \TEMP and \TMP on all drives taking the first # one it finds. # wineconf doesn't support floppy drives, network drives, printers, # and serial device configuration is hardcoded and not configured for # the machine it runs on. Similarly, spy parameters are hard coded. # It would make sense to incorporate much of the heuristic code in # this program into a library to be shared with a dosemu configuration # program, because it seems that at least some of the same stuff will # be wanted. The program needs to be cleaned up still. A better tmp # search algorithm could be written. A fast option is planned. Less # Linux-dependence is desired. Should look for devices independent # of /etc/fstab; then sanity checks on /etc/fstab can be performed. use Getopt::Long; use File::Basename; use strict; use Carp; GetOptions('windir=s', 'sysdir=s', 'thorough', 'debug:s', 'inifile=s') || &Usage; print "WINE REGISTRY Version 2\n"; print ";; All keys relative to \\\\Machine\\\\Software\\\\Wine\\\\Wine\\\\Config\n\n"; &ReadFSTAB(); &FindWindowsDir(); &ReadAutoexecBat(); &StandardStuff(); sub Usage { print "Usage: $0 \n"; # print "-fstab Location of alternate fstab file\n"; print "-windir Location of windows dir in DOS space\n"; print "-thorough Do careful analysis (default)\n"; print "-sysdir Location of systems dir in DOS space\n"; print "-inifile Path to the wine.ini file (by default './wine.ini')\n"; # print "-tmpdir Location of tmp directory\n"; print "Generates (to STDOUT) a wine configuration file based on\n"; print "/etc/fstab and searching around in DOS directories\n"; print "The options above can override certain values\n"; print "This should be considered ALPHA code\n"; exit(0); } sub ReadFSTAB { $::opt_f = $::opt_f ? $::opt_f : '/etc/fstab'; open(FSTAB, $::opt_f) || die "Cannot read $::opt_f\n"; while() { next if /^\s*\#/; next if /^\s*$/; my ($device, $mntpoint, $type, @rest) = split(' ', $_); if ($device !~ m"^/dev/fd") { if ($type eq "ntfs") { push(@::FatDrives, [$device, $mntpoint, 'win95']); } elsif ($type eq "msdos" || $type eq "vfat") { push(@::FatDrives, [$device, $mntpoint, $type]); } elsif ($type eq "iso9660" || ($mntpoint eq "/cdrom" && ! $type eq 'supermount') || ($device eq '/dev/cdrom' && $type eq 'auto') ) { push(@::CdromDrives, [$device, $mntpoint, 'win95']); } elsif ( ($mntpoint eq '/mnt/cdrom' || $mntpoint eq '/cdrom') && $type eq 'supermount') { push(@::CdromDrives, [ '/dev/cdrom', $mntpoint, 'win95']); } } } if (!@::FatDrives) { warn "ERROR ($0): Cannot find any MSDOS drives.\n"; warn "This does not mean you cannot run Wine, but $0\n"; warn "cannot help you (yet)\n"; exit(1); } push(@::UnixDrives, ['', '/tmp', 'hd']); push(@::UnixDrives, ['', '${HOME}', 'network']); my $MagicDrive = 'C'; @::FatDrives = sort byDriveOrder @::FatDrives; @::CdromDrives = sort byCdOrder @::CdromDrives; foreach my $FatDrive (@::FatDrives) { print "[Drive $MagicDrive]\n"; my $MntPoint = $FatDrive->[1]; my $FileSys = $FatDrive->[2]; print "\"Path\" = \"$MntPoint\"\n"; print "\"Type\" = \"hd\"\n"; print "\"Filesystem\" = \"$FileSys\"\n"; print "\n"; &RegisterDrive($MagicDrive, $FatDrive); if(!&IsMounted($FatDrive->[0])) { warn "WARNING: DOS Drive $MagicDrive (" . $FatDrive->[0] . ") is not mounted\n"; } $MagicDrive++; } foreach my $CdromDrive (@::CdromDrives) { print "[Drive $MagicDrive]\n"; my $Device = $CdromDrive->[0]; my $MntPoint = $CdromDrive->[1]; my $FileSys = $CdromDrive->[2]; print "\"Path\" = \"$MntPoint\"\n"; print "\"Type\" = \"cdrom\"\n"; print "\"Device\" = \"$Device\"\n"; print "\"Filesystem\" = \"$FileSys\"\n"; print "\n"; &RegisterDrive($MagicDrive, $CdromDrive); $MagicDrive++; } foreach my $UnixDrive (@::UnixDrives) { print "[Drive $MagicDrive]\n"; my $MntPoint = $UnixDrive->[1]; my $Type = $UnixDrive->[2]; print "\"Path\" = \"$MntPoint\"\n"; print "\"Type\" = \"$Type\"\n"; print "\"Filesystem\" = \"win95\"\n"; print "\n"; $MagicDrive++; } } sub FindWindowsDir { my($MagicDrive) = 'C'; my(@FATD)=@::FatDrives; my(@wininis) = (); my ($winini); my ($ThisDrive); if (!$::opt_windir && !$::opt_fast && !$::opt_thorough) { $::opt_thorough++; } if ($::opt_windir) { $winini = &ToUnix($::opt_windir); if (!-e $winini) { die "ERROR: Specified winini file does not exist\n"; } } elsif ($::opt_fast) { die "-fast code can be implemented\n"; } elsif ($::opt_thorough) { if ($::opt_debug) { print STDERR "DEBUG: Num FATD = ", $#FATD+1, "\n"; } foreach $ThisDrive (@FATD) { my $MntPoint = $ThisDrive->[1]; push(@wininis, `find $MntPoint -iname win.ini -print`); } foreach $winini (@wininis) { chomp $winini; } my ($winini_cnt) = $#wininis+1; if ($::opt_debug) { print STDERR "DEBUG: Num wininis found: $winini_cnt\n";} if ($winini_cnt > 1) { warn "$winini_cnt win.ini files found:\n"; @wininis = sort byFileAge @wininis; warn join("\n", @wininis), "\n"; $winini = $wininis[0]; warn "Using most recent one: $winini\n"; } elsif ($winini_cnt == 0) { die "ERROR: No win.ini found in DOS partitions\n"; } else { $winini = $wininis[0]; } } else { die "ERROR: None of -windir, -fast, or -thorough set\n"; } $::windir = &ToDos(dirname($winini)); print "[wine]\n"; print "\"windows\" = ", &marshall ($::windir), "\n"; if ($::opt_sysdir) { print "\"system\" = ", &marshall ($::opt_sysdir), "\n"; } else { print "\"system\" = ", &marshall ("$::windir\\SYSTEM"), "\n"; } } # Returns 1 if the device is mounted; -1 if mount check failed; 0 if not # mounted. # This code is Linux specific, and needs to be broadened. sub IsMounted { my($Device) = @_; if (-d "/proc") { if (-e "/proc/mounts") { open(MOUNTS, "/proc/mounts") || (warn "Cannot open /proc/mounts, although it exists\n" && return -1); while() { if (/^$Device/) { return 1; # Tested 1.4 } } return 0; # Tested 1.4 } } return -1; } sub RegisterDrive { my($DOSdrive, $Drive) = @_; $::DOS2Unix{$DOSdrive} = $Drive; $::Device2DOS{$Drive->[0]} = $DOSdrive; $::MntPoint2DOS{$Drive->[1]} = $DOSdrive; $::DOS2MntPoint{$DOSdrive} = $Drive->[1]; $::DOS2Device{$DOSdrive} = $Drive->[0]; } sub ReadAutoexecBat { if (!%::DOS2Unix) { &ReadFSTAB; } my($DriveC) = $::DOS2MntPoint{"C"}; $DriveC =~ s%/$%%; my($path); if ($::opt_debug) { print STDERR "DEBUG: Looking for $DriveC/autoexec.bat\n"; } if (-e "$DriveC/autoexec.bat") { # Tested 1.4 open(AUTOEXEC, "$DriveC/autoexec.bat") || die "Cannot read autoexec.bat\n"; while() { s/\015//; if (/^\s*(set\s+)?(\w+)\s*[\s\=]\s*(.*)$/i) { my($varname) = $2; my($varvalue) = $3; chomp($varvalue); $varname =~ tr/A-Z/a-z/; while ($varvalue =~ /%(\w+)%/) { my $matchname = $1; my $subname = $1; $subname =~ tr/A-Z/a-z/; if (($::opt_debug) && ($::opt_debug =~ /path/i)) { print STDERR "DEBUG: Found $matchname as $subname\n"; print STDERR "DEBUG: Old varvalue:\n$varvalue\n"; print STDERR "DEBUG: Old subname value:\n" . $::DOSenv{$subname} . "\n"; } if ($::DOSenv{$subname}) { $varvalue =~ s/\%$matchname\%/$::DOSenv{$subname}/; } else { warn "DOS environment variable $subname not\n"; warn "defined in autoexec.bat. (Reading config.sys\n"; warn "is not implemented.) Using null value\n"; $varvalue =~ s/%$matchname%//; } if (($::opt_debug) && ($::opt_debug =~ /path/i)) { print STDERR "DEBUG: New varvalue:\n$varvalue\n"; } } if ($::opt_debug) { print STDERR "DEBUG: $varname = $varvalue\n"; } $::DOSenv{$varname} = $varvalue; } } close(AUTOEXEC); } else { # Tested 1.4 warn "WARNING: C:\\AUTOEXEC.BAT was not found.\n"; } if ($::DOSenv{"path"}) { my @pathdirs = split(/\s*;\s*/, $::DOSenv{"path"}); if (($::opt_debug) && ($::opt_debug =~ /path/i)) { print STDERR "DEBUG (path): @pathdirs\n"; } foreach my $pathdir (@pathdirs) { if (-d &ToUnix($pathdir)) { if ($::DOSpathdir{$pathdir}++) { warn "Ignoring duplicate DOS path entry $pathdir\n"; } else { if (($::opt_debug) && ($::opt_debug =~ /path/i)) { print STDERR "DEBUG (path): Found $pathdir\n"; } push(@::DOSpathlist, $pathdir); } } else { warn "Ignoring DOS path directory $pathdir, as it does not\n"; warn "exist\n"; } } print "\"path\" = ", &marshall (join (";", @::DOSpathlist)), "\n"; } else { # Code status: tested 1.4 warn "WARNING: Making assumptions for PATH\n"; warn "Will scan windows directory for executables and generate\n"; warn "path from that\n"; my $shellcmd = 'find ' . &ToUnix($::windir) . " -iregex '" . '.*\.\(exe\|bat\|com\|dll\)' . "' -print"; if ($::opt_debug) { print STDERR "DEBUG: autoexec.bat search command:\n $shellcmd\n"; } push(@::DOScommand, `$shellcmd`); if ($::opt_debug && $::opt_debug =~ /autoexec/i) { print STDERR "DEBUG: autoexec.bat search results:\n\@DOS::command\n"; } foreach my $command (@::DOScommand) { $command =~ s%[^/]+$%%; $::DOSexecdir{&ToDos($command)}++; } print "\"path\" = " . &marshall (join(";", grep(s%\\$%%, sort {$::DOSexecdir{$b} <=> $::DOSexecdir{$a}} (keys %::DOSexecdir)))) . "\n"; } if ($::DOSenv{"temp"} && -d &ToUnix($::DOSenv{"temp"})) { print "\"temp\" = ", &marshall ($::DOSenv{"temp"}), "\n"; } else { my $TheTemp; warn "WARNING: Making assumptions for TEMP\n"; warn "Looking for \\TEMP and then \\TMP on every drive\n"; # Watch out .. might pick CDROM drive :-) foreach my $DOSdrive (keys %::DOS2Unix) { my $tmp = &ToUnix("$DOSdrive:\\temp"); if (-d $tmp) { $TheTemp = "$DOSdrive:\\temp"; last; } $tmp = &ToUnix("$DOSdrive:\\tmp"); if (-d $tmp) { $TheTemp = "$DOSdrive:\\tmp"; last; } } $TheTemp = '/tmp' if (!$TheTemp && -d '/tmp'); if ($TheTemp) { warn "Using $TheTemp\n"; print "\"temp\" = ", &marshall ($TheTemp), "\n"; } else { warn "Using C:\\\n"; print "\"temp\" = ", &marshall ("C:\\"), "\n"; } } print "\n"; } # FNunix = &ToUnix(FNdos); # Converts DOS filenames to Unix filenames, leaving Unix filenames # untouched. sub ToUnix { my($FNdos) = @_; my($FNunix); # Initialize tables if necessary. if (!%::DOS2Unix) { &ReadFSTAB; } # Determine which type of conversion is necessary if ($FNdos =~ /^([A-Z])\:(.*)$/) { # DOS drive specified $FNunix = $::DOS2MntPoint{$1} . "/$2"; } elsif ($FNdos =~ m%\\%) { # DOS drive not specified, C: is default $FNunix = $::DOS2MntPoint{"C"} . "/$FNdos"; } else { # Unix filename $FNunix = $FNdos; } 1 while ($FNunix =~ s%\\%/%); # Convert \ to / $FNunix =~ tr/A-Z/a-z/; # Translate to lower case 1 while ($FNunix =~ s%//%/%); # Translate double / to / return $FNunix; } # FNdos = &ToDOS(FNunix) # Converts Unix filenames to DOS filenames sub ToDos { my($FNunix) = @_; my(@MntList) = keys %::MntPoint2DOS; my ($TheMntPt, $FNdos); foreach my $MntPt (@MntList) { # Scan mount point list to see if path matches if ($FNunix =~ /^$MntPt/) { $TheMntPt = $MntPt; last; } } if (!$TheMntPt) { Carp("ERROR: $FNunix not found in DOS directories\n"); exit(1); } $FNdos = $FNunix; $FNdos =~ s/^$TheMntPt//; $FNdos = $::MntPoint2DOS{$TheMntPt} . ":" . $FNdos; 1 while($FNdos =~ s%/%\\%); return $FNdos; } sub InsertDefaultFile { my ($fileName, $tag) = @_; my $state = 0; if (open(DEFFILE, "$fileName")) { while () { $state = 0 if ($state == 1 && $_ =~ /^[ \t]*\#/o && index($_, "[/$tag]") >= 0); print $_ if ($state == 1); $state = 1 if ($state == 0 && $_ =~ /^[ \t]*\#/o && index($_, "[$tag]" ) >= 0); } close(DEFFILE); } else { print STDERR "Cannot read $fileName\n"; } } sub marshall { my ($s) = @_; $s =~ s/\\/\\\\/g; return "\"$s\""; } sub StandardStuff { if (!$::opt_inifile) { &InsertDefaultFile("./wine.ini", "wineconf"); } else { &InsertDefaultFile($::opt_inifile, "wineconf"); } } sub byFileAge { -M $a <=> -M $b; } sub byDriveOrder { my($DeviceA) = $a->[0]; my($DeviceB) = $b->[0]; # Primary drives come first, logical drives last # DOS User's Guide (version 6) p. 70, IBM version. # If both drives are the same type, sort alphabetically # This makes drive a come before b, etc. # It also makes SCSI drives come before IDE drives; # this may or may not be right :-( my($Alogical, $Blogical); if (substr($DeviceA, 3, 1) >= 5) { $Alogical++; } if (substr($DeviceB, 3, 1) >= 5) { $Blogical++; } if ($Alogical && !$Blogical) { return -1; } elsif ($Blogical && !$Alogical) { return 1; } else { return ($DeviceA cmp $DeviceB); } } sub byCdOrder { my($DeviceA) = $a->[0]; my($DeviceB) = $b->[0]; $DeviceA cmp $DeviceB; }