#!/usr/bin/perl
# (c) Copyright 2009, 2010. CodeWeavers, Inc.
use warnings;
use strict;
# Portable which(1) implementation
sub cxwhich($$;$)
{
my ($dirs, $app, $noexec)=@_;
if ($app =~ /^\//)
{
return $app if ((-x $app or $noexec) and -f $app);
}
elsif ($app =~ /\//)
{
require Cwd;
my $path=Cwd::cwd() . "/$app";
return $path if ((-x $path or $noexec) and -f $path);
}
else
{
foreach my $dir (split /:/, $dirs)
{
return "$dir/$app" if ($dir ne "" and (-x "$dir/$app" or $noexec) and -f "$dir/$app");
}
}
return undef;
}
# Fast dirname() implementation
sub _cxdirname($)
{
my ($path)=@_;
return undef if (!defined $path);
return "." if ($path !~ s!/+[^/]+/*$!!s);
return "/" if ($path eq "");
return $path;
}
# Locate where CrossOver is installed by looking for the directory
# where the cxmenu script is located, unwinding symlinks on the way
sub locate_cx_root(;$)
{
my ($fallback)=@_;
my $argv0=cxwhich($ENV{PATH},$0);
$argv0=$0 if (!defined $argv0);
if ($argv0 !~ m+^/+)
{
require Cwd;
$argv0=Cwd::cwd() . "/$argv0";
}
my $dir=_cxdirname($argv0);
my $bindir=$dir;
$bindir =~ s%/lib$%/bin%;
while (!-x "$bindir/cxmenu" or !-f "$bindir/cxmenu")
{
last if (!-l $argv0);
$argv0=readlink($argv0);
$argv0="$dir/$argv0" if ($argv0 !~ m+^/+);
$dir=_cxdirname($argv0);
$bindir=$dir;
$bindir =~ s%/lib$%/bin%;
}
$bindir =~ s%/(?:\./)+%/%g;
$bindir =~ s%/\.$%%;
$ENV{CX_ROOT}=_cxdirname($bindir);
if ((!-x "$ENV{CX_ROOT}/bin/cxmenu" or !-f "$ENV{CX_ROOT}/bin/cxmenu") and
$fallback)
{
$ENV{CX_ROOT}=$fallback;
}
if (!-x "$ENV{CX_ROOT}/bin/cxmenu" or !-f "$ENV{CX_ROOT}/bin/cxmenu")
{
my $name0=$0;
$name0 =~ s+^.*/++;
print STDERR "$name0:error: could not find CrossOver in '$ENV{CX_ROOT}'\n";
exit 1;
}
return $ENV{CX_ROOT};
}
BEGIN {
unshift @INC, locate_cx_root() . "/lib/perl";
}
use CXLog;
use CXUtils;
# Process command-line options
my $opt_verbose;
my $opt_dry_run;
my $opt_help;
require CXOpts;
my $cxopts=CXOpts->new();
$cxopts->add_options(["dry-run!" => \$opt_dry_run,
"verbose!" => \$opt_verbose,
"?|h|help" => \$opt_help
]);
my $err=$cxopts->parse();
CXLog::fdopen(2) if ($opt_verbose);
# Validate the command line options
my $usage;
if ($err)
{
cxerr("$err\n");
$usage=2;
}
elsif ($opt_help)
{
$usage=0;
}
# Print usage
if (defined $usage)
{
my $name0=cxname0();
if ($usage)
{
cxerr("try '$name0 --help' for more information\n");
exit $usage;
}
print "Usage: $name0 [--dry-run] [--verbose] [--help]\n";
print "\n";
print "Sends statistics to CodeWeavers.\n";
print "\n";
print "Options:\n";
print " --dry-run Don't modify the usage log or send any data\n";
print " --verbose Output more information about what is going on\n";
print " --help, -h Shows this help message\n";
exit 0;
}
sub get_os_distro()
{
my $platform_info = "";
my $fh;
if (open($fh, "-|", "cat /etc/*-release"))
{
$platform_info = join"",<$fh>;
close($fh);
}
return $platform_info;
}
# First lock the file to avoid races
require CXBottle;
my ($cxrwconfig, $lock)=CXBottle::lock_and_get_rw_config();
sub release_lock()
{
CXUtils::cxunlock($lock);
# $cxrwconfig must really not be reused once the lock has been released,
# otherwise we will have races with non-cxreportusage scripts.
$cxrwconfig=undef;
}
my $user_dir=CXBottle::get_user_dir();
if (!$user_dir)
{
cxerr("unable to get the user directory\n");
exit 1;
}
my $log="$user_dir/usage.log";
my $tosend="$log.tosend";
# Don't read settings from cxrwconfig. See CXBottle::lock_and_get_rw_config()
my $cxconfig=CXBottle::get_crossover_config();
if (!$cxconfig->get("CrossOver", "ReportWineUsage"))
{
# The user changed his mind and does not want the reporting anymore.
# So delete the usage log.
if ($opt_dry_run)
{
print "Reporting has been disabled so the log will be deleted and nothing will be sent\n";
}
else
{
unlink $log;
unlink $tosend;
}
release_lock();
exit 0;
}
my $now=time();
my $next_report=$cxconfig->get("CrossOver", "NextUsageReportDate", "0");
$next_report =~ s/^0*(?=.)//; # treat as a decimal integer
my $report=eval "$now > $next_report"; # catch integer overflows, etc.
$report=1 if ($@);
if (!$report)
{
# Probably this script is already running -- we don't want to do it twice.
release_lock();
exit 0;
}
# Report more often in trial mode because some trial users may not
# even use it a full week - if there are broken things during that first
# week, it's still useful to know what so we can fix it.
if (! CXUtils::license_file_present())
{
my $cxconfig=CXBottle::get_crossover_config();
my $firstUsageReportDate = $cxconfig->get("CrossOver", "FirstUsageReportDate", 0);
if (! $firstUsageReportDate)
{
$cxrwconfig->set("CrossOver", "FirstUsageReportDate", $now);
}
elsif( ($firstUsageReportDate + 60*60) > $now )
{
# Every 10 min for the first hour, in case someone only uses
# the trial for a very brief period.
$cxrwconfig->set("CrossOver", "NextUsageReportDate", $now + 10*60);
}
elsif( ($firstUsageReportDate + 24*60*60) > $now )
{
# Once per hour the first day.
$cxrwconfig->set("CrossOver", "NextUsageReportDate", $now + 60*60);
}
elsif( ($firstUsageReportDate + 15*24*60*60) > $now )
{
# Once per day until the end fo the demo period.
$cxrwconfig->set("CrossOver", "NextUsageReportDate", $now + 24*60*60);
}
else
{
# Shouldn't really get here ...
$cxrwconfig->set("CrossOver", "NextUsageReportDate", $now + 7*24*60*60);
}
}
else
{
# For now, we will mark the date of the next report a week in the future.
# If this reporting fails, we'll reschedule for tomorrow instead.
$cxrwconfig->set("CrossOver", "NextUsageReportDate", $now + 7*24*60*60);
}
my $anonymousid=$cxconfig->get("CrossOver", "AnonymousId");
if (!$anonymousid)
{
$anonymousid=CXUtils::get_unique_id($ENV{CX_ROOT});
$cxrwconfig->set("CrossOver", "AnonymousId", $anonymousid);
}
if (!$opt_dry_run and !$cxrwconfig->save())
{
cxwarn("unable to save '", $cxrwconfig->get_filename(), "': $!\n");
}
# We can release the lock now: sending the log may take us some time but we
# have one week before another process tries to send this log.
release_lock();
# 'Rotate' the log
if (!-f $tosend)
{
if ($opt_dry_run)
{
$tosend=$log;
}
elsif (-f $log and !rename($log, $tosend))
{
cxerr("unable to rename '$log': $!\n");
exit 1;
}
if (!-f $tosend)
{
# Presumably no application was run since last time.
print "Nothing to send\n" if ($opt_dry_run);
exit 0;
}
}
if (open(my $fh, "<", $tosend))
{
require CXXMLDOM;
my $err=CXXMLDOM::get_xml_load_error();
if ($err)
{
cxerr("unable to use XML::DOM:\n$err\n");
exit 1;
}
my $doc=CXXMLDOM::parse_xml_string("<wineusage version=\"2\"></wineusage>");
my $xml=$doc->getFirstChild();
while (my $line=<$fh>)
{
if ($line =~ m!^exit ([0-9a-fA-F]+) ([0-9]+) ([0-9]+) (.*)[\\/]([^\\/]+) (.*)\n$!)
{
my ($exitcode, $duration, $bottleid, $path, $exe, $appid)=($1, $2, $3, $4, $5, $6);
if ($exe !~ /^(?:explorer|regedit|services|uninstaller|winedevice|winepath|winewrapper)\.exe$/i)
{
my $run=CXXMLDOM::add_new_element($xml, "run");
CXXMLDOM::add_new_element($run, "bottle", $bottleid);
CXXMLDOM::add_new_element($run, "exe", $exe);
CXXMLDOM::add_new_element($run, "path", $path);
CXXMLDOM::add_new_element($run, "exitcode", $exitcode);
CXXMLDOM::add_new_element($run, "duration", $duration);
CXXMLDOM::add_new_element($run, "appid", $appid);
}
}
elsif ($line =~ m!^install ([0-9a-zA-Z\.]+) ([0-9]+) ([0-9]+)\n$!)
{
my $install=CXXMLDOM::add_new_element($xml, "install");
my ($appid, $cancelled, $installfailure)=($1, $2, $3);
CXXMLDOM::add_new_element($install, "appid", $appid);
CXXMLDOM::add_new_element($install, "cancelled", $cancelled);
CXXMLDOM::add_new_element($install, "failluredetected", $installfailure);
}
else
{
cxlog("unknown line format: $line");
}
}
close($fh);
my $pos=$xml->getFirstChild();
if ($pos)
{
# We found application runs to report
CXXMLDOM::add_new_element($xml, "date", time(), $pos);
CXXMLDOM::add_new_element($xml, "product", CXUtils::get_builtin_product_id(), $pos);
CXXMLDOM::add_new_element($xml, "cxversion", CXUtils::get_product_version(), $pos);
CXXMLDOM::add_new_element($xml, "platform", "Linux", $pos);
CXXMLDOM::add_new_element($xml, "osversion", get_os_distro(), $pos);
CXXMLDOM::add_new_element($xml, "anonymousid", $anonymousid, $pos);
CXXMLDOM::add_new_element($xml, "bottlecount", CXBottle::count_user_bottles(), $pos);
# This doesn't check the validity of the license file,
# just uses the file's presence as a proxy for whether
# we are in trial mode (so we can learn what programs may fail
# during trial periods, and / or how many users experience
# failures during trial and give up).
my $licensefilepresent = CXUtils::license_file_present() ? "yes" : "no";
CXXMLDOM::add_new_element($xml, "trialmodelikely", $licensefilepresent, $pos);
my $xml_pi=$doc->createXMLDecl("1.0", "UTF-8", "yes");
my $xml_string=join("", $xml_pi->toString(), "\n", $doc->toString());
if ($opt_dry_run)
{
print $xml_string;
Loading ...