Dual-license your content for inclusion in The Perl 5 Wiki using this HOWTO, or join us for a chat on irc.freenode.net#PerlNet.

Melbourne Perl Mongers/Meeting History 2008 07

From PerlNet

Jump to: navigation, search

Contents

Preparing for a possible Win32 port - David Dick

Reasons for a Win32 port

  • Writing unix style applications, wondering what happens when customer wants it on windows
  • I don't want to write for Win32, but i wouldn't mind if eventually porting my application to Win32 involved minimal effort and suffering
  • I'm writing some some of application may have at least a partial Win32 component


General porting preparation

  • perldoc perlport - First port of call for differences between platforms
  • virtual machines - easy rollback after altering the operating system


This talk will cover

  • Filesystem Locations
  • Logging
  • Process Creation
  • Daemons
  • Security
  • Build Process


Filesystem Locations (Binaries)

  • Win32 root paths are unknown until runtime
my ($base);
if ($^O eq 'MSWin32') {
# usually 'C:\Program Files';
# due to L10n 'Program Files' can be anything
# due to installer choices 'C:\' can also be anything
        $base = Win32::GetFolderPath(Win32::CSIDL_PROGRAM_FILES());
} else {
        $base = '/opt';
}
return File::Spec->catdir($base, $organisation, $product);


Filesystem Locations (Data)

my ($base);
if ($^O eq 'MSWin32') {
# XP is 'C:\Documents and Settings\All Users\Application Data'
# Vista is 'C:\ProgramData';
        $base = Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA());
} else {
        $base = '/var/opt';
}
return File::Spec->catdir($base, $organisation, $product);


Filesystem Locations (Config)

  • All /etc is stored in the Registry
  • Win32API::Registry && Win32::TieRegistry
  • /etc/opt is in the Registry in HKEY_LOCAL_MACHINE\SOFTWARE


Filesystem Locations (Config)

my ($key, $type, $value);
unless (Win32API::Registry::RegOpenKeyEx(
                        Win32API::Registry::HKEY_LOCAL_MACHINE(),
                        "SOFTWARE\\$organisation\\$product",
                        0,
                        Win32API::Registry::KEY_READ(),
                        $key
                                        ))
{
        die("Failed to open registry:$^E");
}
unless (Win32API::Registry::RegQueryValueEx(
                                                $key,
                                                $name,
                                                [],
                                                $type,
                                                $value
                                        ))
{
        die("Failed to read from registry:$^E");
}
unless (Win32API::Registry::RegCloseKey($key)) {
        die("Failed to close registry:$^E");
}


Filesystem Locations (Recommendations)

  • Abstract all references to file system paths
  • Abstract Config access
  • Probably need a initialise method, and a get/cache method
my ($config) = Config->initialise();
my ($value) = $config->get($name);


Log Messages

  • Logging with Win32 looks similar, but...
my ($event_log) = Win32::EventLog->new('product');
$event_log->Report({
          EventType => Win32::EventLog::EVENTLOG_ERROR_TYPE(),
          EventID => 11439,
          Strings => "$path\0$^E",
                });

Log Messages

  • is a little bit more complicated
  • Win32 logging involves an EventId (numeric) with parameters
  • To show the user the event in the event viewer ...


Log Messages

  • ... you need to create a message definition file ...
open (MESSAGES, ">product.mc");
print MESSAGES <<_MC_FILE_;

MessageId=11439
Language=English
Failed to open %1 for writing:%2
.

_MC_FILE_ # must have a trailing empty line
close(MESSAGES);


Log Messages

  • ... and compile it into a (resource) DLL ...
# set PATH to include mc & rc
system('mc product.mc'); # requires platform sdk
system('rc -r -fo product.res product.rc'); # requires platform sdk
# remember to call vcvars32.bat, vcvarsall.bat or whatever before linking
# link requires visual c++ express edition
system('link -dll -noentry -out:product.dll product.res');


Log Messages

  • ... and register the DLL in the registry ...
Root = HKEY_LOCAL_MACHINE
Key = SYSTEM\CurrentControlSet\Services\EventLog\Application\product
Name = EventMessageFile
Value = $path_of_product_dll


Log Messages (Problems)

  • Unix localizes and logs
  • Win32 logs and localizes
  • Disaster for Error Messages to change on a new version


Log Messages (Cheating)

  • Could use '%1'
  • use Sys::Syslog::Win32();


Process Creation

  • There is no fork or exec
  • perlfork describes how fork without exec is simulated via threads.
  • Cygwin is just awesomely evil by having a real fork for windows.
  • Win32::Process describes the native process creation interface


Process Creation - Win32::Process::Create

  • Easy to make mistakes here
my ($process);
Win32::Process::Create(
        $process,
        'C:\Perl\bin\perl.exe', # fully qualified + suffix
        "perl -w $full_path_to_child_perl_script",
        0,
        Win32::Process::NORMAL_PRIORITY_CLASS(),
        $ENV{windir},
                );


Process Creation

  • perl creates a window
  • wperl does not create a window
  • system("$program $parameter1 $parameter2") works
  • as does open("$program $parameter1 $parameter2 |")
  • kill(0, $pid) works. Sort of. No EPERM support.
  • for backgrounding, launch with Win32::Process
  • Monitor processes with Win32::Process::Info


Process Creation - waitpid

  • to monitor a Win32::Process process
my ($exit_code);
$process->GetExitCode($exit_code);
if ($exit_code == Win32::Process::STILL_ACTIVE()) {
        return undef;
} else {
        return $exit_code;
}


Process Creation - Monitoring

  • Getting a complete process listing
my ($pi) = Win32::Process::Info->new();
foreach my $info ($pi->GetProcInfo()) {
        print $info->{ProcessId} . ":" . $info->{CommandLine} . "\n";
}


Process Creation - Multi-tasking

  • perldoc perlfork
  • use threads;
  • which seem to come down to the same thing
  • Single threaded / Non blocking. Okay for exclusive network operations.
*POSIX::EWOULDBLOCK = sub() { return 10035; };
*POSIX::EINPROGRESS = sub() { return 10036; };

Process Creation Recommedations

  • abstract process creation to only allow an executable + parameters type interface
  • abstract existance of a pid
     my ($pid) = Process->create('program.pl', @arguments);
     if (Process->alive($pid)) {
         ...
     } else {
         ...
     }


Daemons

  • Win32 Daemons are called Services
  • Services start/stop via API calls, NOT signals
  • Must respond via API to instructions in 30 secs
  • Win32::Daemon allows access to this API
  • written by Dave Roth
  • stored at roth.net


Services (Initialising)

if ($^O eq 'MSWin32') {
        require Win32::Daemon;
        STARTING_SERVICE: {
                Win32::Daemon::StartService();
                my ($state) = Win32::Daemon::State();
                unless ($state) {
                        sleep $sleep_time;
                        redo STARTING_SERVICE;
                }
        }
} else {
        require Proc::Daemon;
        Proc::Daemon::Init();
}


Services (Status Checking)

my $state;
STATE_CHECK: {
        $state = Win32::Daemon::State();
        if ($state == Win32::Daemon::SERVICE_STOP_PENDING()) {
                Win32::Daemon::State(Win32::Daemon::SERVICE_STOPPED());
        } elsif ($state == Win32::Daemon::SERVICE_PAUSE_PENDING()) {
                Win32::Daemon::State(Win32::Daemon::SERVICE_PAUSED());
                sleep $sleep_time;
                redo STATE_CHECK;

        ... etc ...

        } elsif ($state == Win32::Daemon::SERVICE_RUNNING()) {
        }
}
return $state;


Services (Starting and Stopping)

  • command line access via 'net start|stop <servicename>
  • GUI access via 'Services' in 'Administrative Tools'


Services (Environment Variables)

  • Custom Environment variables are awkward
  • With Services, Environment variables are only updated after a reboot
  • Custom environment variables == reboot after every installation
  • Think PATH, ORACLE_ROOT, etc


Services (Background Processes)

  • think of a background user process interacting with the desktop
  • launched at login time
  • ssh-agent???
  • Create a name at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
  • with a value of the command to run
  • must run as that process. Do not attempt to background this process


Security (users)

  • Root/LOCAL_SYSTEM on Win32 is not all-powerful
  • difficult for LOCAL_SYSTEM to interact with the Desktop
  • Therefore there is no easy way to "drop privileges"
  • "drop privileges" means logging on as that user
  • which requires a username AND password


Security (SIDs)

  • Stands for Security Identifiers
  • Like a gid AND uid
  • Always the same, across all versions Win32/L10n
  • S-1-1-0 = Everyone
  • S-1-5-18 = Local System


Security (who am i?)

  • Win32 equivalent of (getpwuid($>))[0])
my ($process_info) = Win32::Process::Info->new();
my ($current_process) = ${$process_info->GetProcInfo($$)}[0];
$user_name = $current_process->{'Owner'};


Security (filesystem)

  • Powerful/complex permissions system involving multiple groups
  • Very difficult to manually audit effectively
  • Very easy to audit programmatically
  • use Win32::FileSecurity();
  • sort of combines chown and chmod into one call


Security (filesystem)

my (%permissions);
my (@array_of_permissions) = (
               'STANDARD_RIGHTS_READ',
               'R',
               'READ',
               'GENERIC_READ'
                             );
my (@another_array_of_permissions) = ( .. );
my ($user_mask) = Win32::FileSecurity::MakeMask(@array_of_permissions);
my ($group_mask) = Win32::FileSecurity::MakeMask(@another_array);
$permissions{$user_name} = $user_mask;
$permissions{$group_name} = $group_mask;
unless (Win32::FileSecurity::Set($file_path, \%permissions)) {
        die("Failed to chmod/chown $path");
}


Security (Temp Files)

  • Each windows user has it's own temp directory
  • no /dev/fd/ equivalent - Roll your own??
#
# NOTE : This section of code has never been used to 
#        accomplish anything useful.  It's place here
#        is to serve as a possible jumping off point 
#        for anyone interested in pursuing this idea.
#        You should also need to use the inherit flag
#        in Win32::Process::Create
#
        my $tmp_handle = File::Temp::tempfile();
        my $os_handle = Win32API::File::GetOsFHandle($tmp_handle);
        unless ($os_handle) {
                die("Failed to get OS File Handle:$^E");
        }
        unless (Win32API::File::SetHandleInformation(
                        $os_handle,
                        Win32API::File::HANDLE_FLAG_INHERIT(),
                        1
                                                ))
        {
                die("Failed to set handle information:$^E");
        }


Build Process (Wix)

  • open source project at sourceforge
  • builds native windows packages
  • fulls much the same role as rpm/dpkg
  • That is, no mechanism for retrieving dependencies


Build Process (Wix)

  • Version 2 is stable/documented
  • Version 3 is development/undocumented


Build Process (Wix)

  • build an XML describing your product
  • Very good documentation (at least for stable)
  • use wix binaries to create msi package from XML
  • building an entire perl distribution increases package size by about 8mb
  • Vista seems to have toughest security model


Build Process (Dependencies)

  • Remember to depend on Win32 capable libraries
  • Check dependencies for a Win32 Makefile
  • for example 'win32/makefile.mk' in perl source
  • Estimate time required to port it youself if required
  • Some projects advertise Win32 compatibility when they mean Cygwin